One of UGUI Selectable (Selectable and Button)

        Selectable is the core component of UGUI. In addition to the most commonly used Button, it is also the base class of components such as Scrollbar, Dropdown, Slider, Toggle, and InputField. In this article, we will analyze the source code of Selectable and Button and explore their implementation principles.
As usual, attach the UGUI source code download address.

        Before Selectable, we saw that four attributes were added to it:

[AddComponentMenu("UI/Selectable", 70)]
[ExecuteInEditMode]
[SelectionBase]
[DisallowMultipleComponent]

        AddComponentMenu is to add options to the Component menu, the order is 70. When you click the AddComponent button of a GameObject, a menu will pop up. Click on the UI option, and you can see that the last one is Selectable.
        ExecuteInEditMode indicates that the component will be executed under the editor.

        SelectionBase marks this object as the selection base object.

        ​​​​ DisallowMultipleComponent does not allow the same type of components to appear on a GameObject. We cannot add two Selectable components to an object, nor can we add an InputField component to a Button object.

        ​​​Selectable inherits from UIBehaviour and inherits 7 interfaces.
   

public class Selectable
        :
        UIBehaviour,
        IMoveHandler,
        IPointerDownHandler, IPointerUpHandler,
        IPointerEnterHandler, IPointerExitHandler,
        ISelectHandler, IDeselectHandler

        UIBehaviour is the base class of all UI components. It is placed in the EventSystem directory. Except for IsDestroyed, all others are virtual functions. We can also think of it as part of the event system. It adds Awake (when the script The instance is loaded (that is, when AddComponent), OnRectTransformDimensionsChange (when RectTransform dimensions change) and other methods to receive events from UnityEngine.
        Selectable’s Awake obtains a Graphic component instance m_TargetGraphic (the Image component is indirectly inherited from Graphic). If the Transition we choose is Color Tint, when the state of Selectable changes (normal, highlighted, pressed, disabled), the CrossFadeColor method of m_TargetGraphic will be called, which will gradually change the current image to the specified color.

        OnEnable (for the timing of being called, please refer to Untiy3D component tips (1) OnEnabled and OnDisabled), the instance will be added to the static list of Selectables s_List (s_List stores all available Selectables and is used for UI navigation queries) , and then set the status of the instance to Normal or Highlighted (or Disabled).

        OnDisable (see the Untiy3D component tips (1) OnEnabled and OnDisabled for the timing of being called), clear the state of the instance (need to restore colors and pictures, and play normal animation), and remove the instance from s_List.

        In OnDidApplyAnimationProperties (after applying animation properties), InternalEvaluateAndTransitionToSelectionState will be called through the OnSetProperty method to refresh the current state.

        In OnCanvasGroupChanged (when the CanvasGroup changes), the interactable of the new GanvasGroup will be judged. If the interactable of the GanvasGroup is false, then the Selectable itself will be disabled. Next, refresh the current state.

        ​ ​ ​ Then let’s look at several interfaces inherited by Selectable. You can first look at the UGUI kernel exploration (3) input module to understand the triggering timing of these interfaces.

        ​​​​Inherited from IMoveHandler and need to implement the OnMove method. Depending on the movement direction, navigate to the next Selectable component.

        ​​​​Inherited from IPointerDownHandler and need to implement the OnPointerDown method. Call EventSystem.current.SetSelectedGameObject to set itself as the currently selected object (it will call its own OnSelect and the predecessor object's OnDeselect), mark isPointerDown as true and refresh the state (when both isPointerInside and isPointerDown are true, it is the Pressed state).

        ​ ​ ​​Inherited from IPointerUpHandler and need to implement the OnPointerUp method. Mark isPointerDown as false and refresh the state.

        ​​​​Inherited from IPointerEnterHandler and need to implement the OnPointerEnter method. Mark isPointerInside as true and refresh the state.

        ​​​​​Inherited from IPointerExitHandler and need to implement the OnPointerExit method. Mark isPointerInside as false and refresh the state.

        ​​​​Inherited from ISelectHandler and need to implement the OnSelect method. Mark hasSelection as true and refresh the state (when hasSelection is true, it is the Highlighted state. In addition, isPointerInside and isPointerDown are also the basis for judging the Highlighted state, which will be mentioned later).

        ​​​​Inherited from IDeselectHandler and need to implement the OnDeselect method. Mark hasSelection as false and refresh the state.

        IsHighlighted method:

 protected bool IsHighlighted(BaseEventData eventData)
        {
            if (!IsActive())
                return false;
 
            if (IsPressed())
                return false;
 
            bool selected = hasSelection;
            if (eventData is PointerEventData)
            {
                var pointerData = eventData as PointerEventData;
                selected |=
                    (isPointerDown && !isPointerInside && pointerData.pointerPress == gameObject) // This object pressed, but pointer moved off
                    || (!isPointerDown && isPointerInside && pointerData.pointerPress == gameObject) // This object pressed, but pointer released over (PointerUp event)
                    || (!isPointerDown && isPointerInside && pointerData.pointerPress == null); // Nothing pressed, but pointer is over
            }
            else
            {
                selected |= isPointerInside;
            }
            return selected;
        }

      The parameter is the event data passed by the input module, which is mainly used to determine whether the event response object is this object (or null).
        The above OnPointerDown and other methods evaluate and refresh the state through the EvaluateAndTransitionToSelectionState method. In this method, UpdateSelectionState calls IsPressed and IsHighlighted to determine the current state. InternalEvaluateAndTransitionToSelectionState determines whether the current component is disabled, and then calls the DoStateTransition method.

 protected virtual void DoStateTransition(SelectionState state, bool instant)
        {
            Color tintColor;
            Sprite transitionSprite;
            string triggerName;
 
            switch (state)
            {
                case SelectionState.Normal:
                    tintColor = m_Colors.normalColor;
                    transitionSprite = null;
                    triggerName = m_AnimationTriggers.normalTrigger;
                    break;
                case SelectionState.Highlighted:
                    tintColor = m_Colors.highlightedColor;
                    transitionSprite = m_SpriteState.highlightedSprite;
                    triggerName = m_AnimationTriggers.highlightedTrigger;
                    break;
                case SelectionState.Pressed:
                    tintColor = m_Colors.pressedColor;
                    transitionSprite = m_SpriteState.pressedSprite;
                    triggerName = m_AnimationTriggers.pressedTrigger;
                    break;
                case SelectionState.Disabled:
                    tintColor = m_Colors.disabledColor;
                    transitionSprite = m_SpriteState.disabledSprite;
                    triggerName = m_AnimationTriggers.disabledTrigger;
                    break;
                default:
                    tintColor = Color.black;
                    transitionSprite = null;
                    triggerName = string.Empty;
                    break;
            }
 
            if (gameObject.activeInHierarchy)
            {
                switch (m_Transition)
                {
                    case Transition.ColorTint:
                        StartColorTween(tintColor * m_Colors.colorMultiplier, instant);
                        break;
                    case Transition.SpriteSwap:
                        DoSpriteSwap(transitionSprite);
                        break;
                    case Transition.Animation:
                        TriggerAnimation(triggerName);
                        break;
                }
            }
        }

           Set the color, picture or animation name according to the state, and then reflect the state change on the UI through the StartColorTween, DoSpriteSwap or TriggerAnimation method.


        ​​​​​Finally, let’s explain the Button component. Button inherits from Selectable and additionally inherits two interfaces, IPointerClickHandler and ISubmitHandler. It also adds an event onClick of type UnityEvent. The onClick event can add user-defined listeners. The specific method can be added through the editor or through onClick.AddListener.

        OnPointerClick will call the Press method to call back onClick.

        OnSubmit will also call the Press method, switch the state to Pressed, and start the coroutine to call OnFinishSubmit to let the state gradually change to the current state (the state obtained through UpdateSelectionState).

        It can be seen that Button adds an interface to respond to click and confirmation events compared to Selectable, and it also opens the onClick event that can be added to user-defined monitoring.

        ​​​The function of Selectable is to provide four state changes based on mouse events. On the one hand, it provides basic logic for derived classes such as Button and Dropdown. On the other hand, we can also derive new custom components based on Selectable.

Guess you like

Origin blog.csdn.net/ttod/article/details/135010923