UIGU源码分析7:Scrollbar

华俊弼
2023-12-01

源码7:Scrollbar

  public class Scrollbar : Selectable, IBeginDragHandler, IDragHandler, IInitializePotentialDragHandler, ICanvasElement
    {
      
        [SerializeField]
        private RectTransform m_HandleRect;

        // Direction of movement.
        [SerializeField]
        private Direction m_Direction = Direction.LeftToRight;


        [Range(0f, 1f)]
        [SerializeField]
        private float m_Value;

        [Range(0f, 1f)]
        [SerializeField]
        private float m_Size = 0.2f;



        [Range(0, 11)]
        [SerializeField]
        private int m_NumberOfSteps = 0;



        [Space(6)]

        [SerializeField]
        private ScrollEvent m_OnValueChanged = new ScrollEvent();

		...
}


Scrollbar,它继承自Selectable,还继承了 IBeginDragHandler, IDragHandler, IInitializePotentialDragHandler, ICanvasElementt四个接口

Scrollbar有一个float变量m_Value表示当前Scrollbar的值,变化范围在[0, 1]之间,还添加了一个UnityEvent类型的事件onValueChanged,用于外部监听m_Value是否改变。


     void Set(float input, bool sendCallback = true)
        {
            float currentValue = m_Value;

            // bugfix (case 802330) clamp01 input in callee before calling this function, this allows inertia from dragging content to go past extremities without being clamped
            m_Value = input;

            // If the stepped value doesn't match the last one, it's time to update
            if (currentValue == value)
                return;

            UpdateVisuals();
            if (sendCallback)
            {
                UISystemProfilerApi.AddMarker("Scrollbar.value", this);
                m_OnValueChanged.Invoke(value);
            }
        }

m_value值的设置主要就是通过Set方法来设置 同时发送m_OnValueChanged 事件。同时还需要调用UpdateVisuals 来刷新显示


        private void UpdateVisuals()
        {
#if UNITY_EDITOR
            if (!Application.isPlaying)
                UpdateCachedReferences();
#endif
            m_Tracker.Clear();

            if (m_ContainerRect != null)
            {
                m_Tracker.Add(this, m_HandleRect, DrivenTransformProperties.Anchors);
                Vector2 anchorMin = Vector2.zero;
                Vector2 anchorMax = Vector2.one;

                float movement = Mathf.Clamp01(value) * (1 - size);
                if (reverseValue)
                {
                    anchorMin[(int)axis] = 1 - movement - size;
                    anchorMax[(int)axis] = 1 - movement;
                }
                else
                {
                    anchorMin[(int)axis] = movement;
                    anchorMax[(int)axis] = movement + size;
                }

                m_HandleRect.anchorMin = anchorMin;
                m_HandleRect.anchorMax = anchorMax;
            }
        }

UpdateVisuals 就是更新Scrollbar的显示 其实主要就是根据Value值 计算m_HandleRect的锚框

这里要讲下DrivenRectTransformTracker

官方解释:https://docs.unity3d.com/ScriptReference/DrivenRectTransformTracker.html

DrivenRectTransformTracker结构用于指定它正在驱动哪个RectTransform,驱动RectTransform意味着被驱动的RectTransform的值由该组件控制。


重写了OnPointerDown

  public override void OnPointerDown(PointerEventData eventData)
        {
            if (!MayDrag(eventData))
                return;

            base.OnPointerDown(eventData);
            isPointerDownAndNotDragging = true;
            m_PointerDownRepeat = StartCoroutine(ClickRepeat(eventData.pointerPressRaycast.screenPosition, eventData.enterEventCamera));
        }

设置isPointerDownAndNotDragging 为True 开启携程ClickRepeat

  protected IEnumerator ClickRepeat(Vector2 screenPosition, Camera camera)
        {
            while (isPointerDownAndNotDragging)
            {
                if (!RectTransformUtility.RectangleContainsScreenPoint(m_HandleRect, screenPosition, camera))
                {
                    Vector2 localMousePos;
                    if (RectTransformUtility.ScreenPointToLocalPointInRectangle(m_HandleRect, screenPosition, camera, out localMousePos))
                    {
                        var axisCoordinate = axis == 0 ? localMousePos.x : localMousePos.y;

                        // modifying value depending on direction, fixes (case 925824)

                        float change = axisCoordinate < 0 ? size : -size;
                        value += reverseValue ? change : -change;
                    }
                }
                yield return new WaitForEndOfFrame();
            }
            StopCoroutine(m_PointerDownRepeat);
        }

ClickRepeat,判断点击事件是否在m_HandleRect外面(一定在Scrollbar里面),如果在外面,就将事件坐标转换到m_HandleRect的本地坐标系里,然后调整value,直到点击事件在m_HandleRect里面

补充一下RectTransformUtility

  1. RectTransformUtility.FlipLayoutAxes 翻转RectTransform大小和对齐的水平和垂直轴,以及可选的子对象。
  2. RectTransformUtility.FlipLayoutOnAxis 沿水平或垂直轴翻转
  3. RectTransform的对齐方式,以及可选的子对象。
  4. RectTransformUtility.PixelAdjustPoint 将屏幕空间中的给定点转换为像素正确的点。
  5. RectTransformUtility.PixelAdjustRect 给定矩形变换,以像素精确坐标返回角点。
  6. RectTransformUtility.RectangleContainsScreenPoint RectTransform是否包含从相机看到的点。
  7. RectTransformUtility.ScreenPointToLocalPointInRectangle 将屏幕空间点转换为给定RectTransform平面上的局部空间中的位置。
  8. RectTransformUtility.ScreenPointToWorldPointInRectangle 将屏幕空间点转换为位于给定RectTransform平面上的世界空间中的位置。
    public override void OnPointerUp(PointerEventData eventData)
        {
            base.OnPointerUp(eventData);
            isPointerDownAndNotDragging = false;
        }

重写OnPointerUp isPointerDownAndNotDragging置为false


重写了Selectable的OnMove

    /// <summary>
    /// Handling for movement events.
    /// </summary>
    public override void OnMove(AxisEventData eventData)
    {
        if (!IsActive() || !IsInteractable())
        {
            base.OnMove(eventData);
            return;
        }

        switch (eventData.moveDir)
        {
            case MoveDirection.Left:
                if (axis == Axis.Horizontal && FindSelectableOnLeft() == null)
                    Set(Mathf.Clamp01(reverseValue ? value + stepSize : value - stepSize));
                else
                    base.OnMove(eventData);
                break;
            case MoveDirection.Right:
                if (axis == Axis.Horizontal && FindSelectableOnRight() == null)
                    Set(Mathf.Clamp01(reverseValue ? value - stepSize : value + stepSize));
                else
                    base.OnMove(eventData);
                break;
            case MoveDirection.Up:
                if (axis == Axis.Vertical && FindSelectableOnUp() == null)
                    Set(Mathf.Clamp01(reverseValue ? value - stepSize : value + stepSize));
                else
                    base.OnMove(eventData);
                break;
            case MoveDirection.Down:
                if (axis == Axis.Vertical && FindSelectableOnDown() == null)
                    Set(Mathf.Clamp01(reverseValue ? value + stepSize : value - stepSize));
                else
                    base.OnMove(eventData);
                break;
        }
    }

主要作用就是当方向键按下并与ScrollBar的方向一致时,便不在导航到下一个Selectable,而是修改value值(加减stepSize),即移动滚动条。


实现了 OnBeginDrag 和OnDrag

   public virtual void OnBeginDrag(PointerEventData eventData)
    {
        isPointerDownAndNotDragging = false;

        if (!MayDrag(eventData))
            return;

        if (m_ContainerRect == null)
            return;

        m_Offset = Vector2.zero;
        if (RectTransformUtility.RectangleContainsScreenPoint(m_HandleRect, eventData.pointerPressRaycast.screenPosition, eventData.enterEventCamera))
        {
            Vector2 localMousePos;
            if (RectTransformUtility.ScreenPointToLocalPointInRectangle(m_HandleRect, eventData.pointerPressRaycast.screenPosition, eventData.pressEventCamera, out localMousePos))
                m_Offset = localMousePos - m_HandleRect.rect.center;
        }
    }

    public virtual void OnDrag(PointerEventData eventData)
    {
        if (!MayDrag(eventData))
            return;

        if (m_ContainerRect != null)
            UpdateDrag(eventData);
    }



  void UpdateDrag(PointerEventData eventData)
    {
        if (eventData.button != PointerEventData.InputButton.Left)
            return;

        if (m_ContainerRect == null)
            return;

        Vector2 position = Vector2.zero;
        if (!MultipleDisplayUtilities.GetRelativeMousePositionForDrag(eventData, ref position))
            return;

        Vector2 localCursor;
        if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(m_ContainerRect, position, eventData.pressEventCamera, out localCursor))
            return;

        Vector2 handleCenterRelativeToContainerCorner = localCursor - m_Offset - m_ContainerRect.rect.position;
        Vector2 handleCorner = handleCenterRelativeToContainerCorner - (m_HandleRect.rect.size - m_HandleRect.sizeDelta) * 0.5f;

        float parentSize = axis == 0 ? m_ContainerRect.rect.width : m_ContainerRect.rect.height;
        float remainingSize = parentSize * (1 - size);
        if (remainingSize <= 0)
            return;   

调用UpdateDrag方法,用于基于鼠标更新Scrollbar的位置。这个方法会计算出m_HandleRect左下角的坐标,根据Direction与剩下的尺寸(就是可滑动区域的尺寸)作比,计算出value。


在OnEnable 的时候重新Set设置m_value值 调用UpdateVisuals刷新函数。这样做的主要目的就是为了在游戏启动的时候 Scrollbar的划块能在正确的位置

OnDisable 调用了Tracker的Clear

 类似资料: