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。
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的四個角的世界座標。