UGUI源码剖析(ScrollRect)

Runtime类图分析

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

 

关键方法

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的边界)。

 

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。

 

EnsureLayoutHasRebuilt

确保布局已经被重建过了

 

StopMovement

设置速度为0停止content的滚动

 

OnScroll继承自IScrollHandler

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

 

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

 

OnBeginDrag继承自IBeginDragHandler

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

 

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

 

OnDrag继承自IDragHandler

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

 

SetContentAnchoredPosition

设置Content的锚点位置

 

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的可见性。

 

UpdateScrollbars

更新ScrollBar的Value值

 

SetHorizontalNormalizedPosition

SetVerticalNormalizedPosition

SetNormalizedPosition

设置归一化的位置值

 

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

 

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

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

UpdateOneScrollbarVisibility

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

 

UpdateScrollbarLayout

更新Scrollbar的布局

 

UpdateBounds()

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

 

AdjustBounds

调整包围盒的边界,将Bounds的坐标和大小调整成合理的值

 

GetBounds

InternalGetBounds

获取边界信息,左上左下右上右下

 

CalculateOffset

InternalCalculateOffset

获取viewBounds和contentBounds的offset

 

重要属性和字段

 

补充知识点

补充知识点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的四个角的世界坐标。

 

优化方向

可以看到ScrollRect中仍然留有许多ScrollBar部分的逻辑,这部分可以考虑拆分出来,保证逻辑的模块独立性

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值