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
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