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
OnEndDrag继承自IEndDragHandler,设置m_Dragging为false。
图中,1为ScrollRect,2为viewRect,3为content。
OnDrag继承自IDragHandler
UpdateBounds()
GetBounds方法将m_Content的四个顶点转换为viewRect坐标系中的点,然后返回一个Bounds边界框,其实就是m_Content相对viewRect的位置和大小,会在调整m_Content位置时用到。然后UpdateBounds会在认为不合理的时候(content宽度或高度比view小),对m_ContentBounds执行额外的调整,将Bounds的坐标和大小调整成合理的值(尺寸和view相同,位置根据pivot调整)。
OnScroll继承自IScrollHandler
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的四个角的世界坐标。