UGUI源码解析(十)ScrollRect

ScrollRect

ScrollRect继承自UIBehaviour,另外还继承了IInitializePotentialDragHandler, IBeginDragHandler, IEndDragHandler, IDragHandler, IScrollHandler, ICanvasElement, ILayoutElement, ILayoutGroup这些接口。

OnEnable方法里添加了m_HorizontalScrollbar和m_VerticalScrollbar的onValueChanged事件的监听,用于监听滚动条的Value变化,以调整内容显示。并将自己注册到CanvasUpdateRegistry的布局重建序列中。

OnDisable方法将自己从CanvasUpdateRegistry的布局重建序列中移除,并移除了m_HorizontalScrollbar和m_VerticalScrollbar的事件监听。设置m_HasRebuiltLayout为false,清除m_Tracker,设置m_Velocity(横纵速度)为0(在LateUpdate中被调用,用于将超出边界的内容移动回来),并通知LayoutRebuilder需要重建Layout。

IsActive除了调用了基类的有效性判断(对象有效并组件激活),还判断了m_Content(内容)不为null。

OnRectTransformDimensionsChange(当RectTransform尺寸发生变化时),调用SetDirty,通知LayoutRebuilder需要重建Layout。

OnInitializePotentialDrag(继承自IInitializePotentialDragHandler)里设置m_Velocity为0。

OnBeginDrag继承自IBeginDragHandler

  • 调用UpdateBounds,调整边界,使内容永远不会超出内容边界,
  • 并将拖拽事件的点转换为viewRect坐标系内的点赋值给m_PointerStartLocalCursor,
  • 将m_Content.anchoredPosition赋值给m_ContentStartPosition。
  • 并设置m_Dragging为true。

OnEndDrag继承自IEndDragHandler,设置m_Dragging为false。

图中,1为ScrollRect,2为viewRect,3为content。

OnDrag继承自IDragHandler

  • 首先把当前拖拽事件的点转换为viewRect坐标系上的点localCursor
  • 调用UpdateBounds,调整边界使内容永远不会超出内容边界
  • 计算差值pointerDelta = localCursor- m_PointerStartLocalCursor
  • 计算content的position = m_ContentStartPosition + pointerDelta + content在viewRect的相对偏移。
  • 调用SetContentAnchoredPosition(position),设置content的position

UpdateBounds()

GetBounds方法将m_Content的四个顶点转换为viewRect坐标系中的点,然后返回一个Bounds边界框,其实就是m_Content相对viewRect的位置和大小,会在调整m_Content位置时用到。然后UpdateBounds会在认为不合理的时候(content宽度或高度比view小),对m_ContentBounds执行额外的调整,将Bounds的坐标和大小调整成合理的值(尺寸和view相同,位置根据pivot调整)。

OnScroll继承自IScrollHandler

  • 调用EnsureLayoutHasRebuilt确保Layout已经被重建,接着UpdateBounds更新边界。
  • 接收鼠标滚动,根据滚动距离计算出m_Content的位置。
  • 调用SetContentAnchoredPosition(position),设置content的位置,接着UpdateBounds更新边界。

Rebuild()

Rebuild继承自ICanvasElement,它在重建Layout的时候被调用。

在PreLayout(预布局)阶段,会调用UpdateCachedData(更新缓存数据,包括m_HorizontalScrollbarRect横向滚动条和m_VerticalScrollbarRect纵向滚动条,m_HSliderExpand是否支持横向滑动展开、m_VSliderExpand是否支持纵向滑动展开、m_HSliderHeight横向滚动条高度、m_VSliderWidth纵向滚动条宽度)。

在PostLayout(后布局)阶段,会更新边界,更新滚动条位置,调用UpdatePrevData(保存之前的数据,m_PrevPosition保存content的位置、m_PrevViewBounds保存view的边界、m_PrevContentBounds保存content的边界)。

 

ScrollRect还继承了ILayoutGroup接口,需要实现SetLayoutHorizontal和SetLayoutVertical两个方法。

SetLayoutHorizontal()

SetLayoutHorizontal里,如果m_HSliderExpand或m_VSliderExpand为true,便强制立刻重建content的布局。然后根据m_VSliderExpand、vScrollingNeeded(content的高度大于view的高度)、m_HSliderExpand和hScrollingNeeded(content的宽度大于view的宽度),计算viewRect的sizeDelta、m_ViewBounds和m_ContentBounds。

SetLayoutVertical()

SetLayoutVertical里调用UpdateScrollbarLayout方法并更新m_ViewBounds和m_ContentBounds。

UpdateScrollbarLayout()

UpdateScrollbarLayout里将横向滚动条的宽度设置为ScrollRect的相同的值(如果有纵向滚动条,减掉其宽度及间隔),将纵向滚动条的高度设置为ScrollRect的相同的值(如果有横向滚动条,减掉其高度及间隔)。

LateUpdate()

ScrollRect还重写了LateUpdate,这个方法是在所有组件Update调用完之后,每一帧都会被调用。在这个方法里,调用EnsureLayoutHasRebuilt确保Layout已经被重建,接着UpdateBounds更新边界。

如果m_Dragging为false,且content已经超出了可滚动范围(如果是Horizontal方向,计算当contentBounds的最小值的x大于viewBounds的最小值的x或contentBounds的最大值的x小于viewBounds的最大值的x时的offset偏移。如果offset不为0,就判定超出了可滚动范围。Vertical方向同理),且m_Velocity速度不为0,当ScrollRect设置为MovementType.Elastic,如果m_Inertia(惯性)为true,便根据m_DecelerationRate计算出一个新的惯性速度m_Velocity,否则应用弹簧物理的阻尼效果。便根据速度逐渐将content的坐标修正为合理的值。当ScrollRect设置为MovementType.Clamped,在ScrollRect原来位置的基础上直接加上Offset偏移。

如果在拖动中m_Dragging为true且m_Inertia(惯性)为true便根据content的当前位置和m_PrevPosition计算出一个新的惯性速度m_Velocity。

然后判断如果m_ViewBounds、m_ContentBounds、m_Content.anchoredPosition和旧数据不同,则更新Scrollbar的位置,发送OnValueChanged,并保存当前数据为旧数据。

最后,调用UpdateScrollbarVisibility更新ScrollBar的可见性。

 

补充知识点sizeDelta:

RectTransform.sizeDelta RectTransform的大小相对于锚点的距离。

如果锚点相同,sizeDelta的大小与RectTransform大小相同,如果锚点在四个父项的四个角中,sizeDelta的大小表示该RectTransform相比于父项的大小多少。

补充知识点RectTransform变换:

定位矩形变换时,首先确定它是否具有任何伸展行为,这很有用。当anchorMin和anchorMax属性不相同时会出现拉伸行为。

对于非拉伸Rect变换,通过设置anchoredPosition和sizeDelta属性可以非常容易地设置位置。anchoredPosition指定枢轴相对于锚点的位置。sizeDelta与没有拉伸时的大小相同。

对于拉伸Rect变换,使用offsetMin和offsetMax属性可以更简单地设置位置。offsetMin属性指定相对于左下角锚点的矩形的左下角。offsetMax属性指定相对于右上角锚点的矩形右上角。

补充知识点RectTransform:

RectTransform.GetWorldCorners 获取RectTransform的四个角的世界坐标。

你可能感兴趣的:(Unity,UGUI源码解析)