您當前的位置:首頁 > 曲藝

UGUI原始碼解析(十)ScrollRect

作者:由 鵝廠程式小哥 發表于 曲藝時間:2021-01-07

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