Flutter ReorderableList Widget Class 源碼解析
Flutter ReoderableList 底層用到 Flutter Sliver 元件,Sliver 系列的元件可以做到更多滑動以及惰性讀取元件的組合,目前較常有的高階 Sliver 元件有 SliverGrid 跟 SliverList。
classDiagram class ReorderableList class ReorderableListState class SliverReorderableList class SliverReorderableListState ReorderableList --> ReorderableListState ReorderableListState --> SliverReorderableList SliverReorderableList --> SliverReorderableListState ReorderableDragStartListener --> MultiDragGestureRecognizer SliverReorderableListState -- MultiDragGestureRecognizer SliverReorderableListState -- OverlayEntry SliverReorderableListState -- _DragInfo SliverReorderableListState -- _ReorderableItemState SliverReorderableListState --> _ReorderableItem _ReorderableItem --> _ReorderableItemState _DragInfo --> _DragItemProxy ReorderableDragStartListener --> SliverReorderableListState
startItemDragReorder
會建立 recognizer 並且啟動 drag,但大多數的應用下不會直接呼叫,而是用 ReorderableDragStartListener
或是 ReorderableDelayedDragStartListener
間接呼叫。
_ReorderableItemState
在自己的初始化階段執行 SliverReorderableListState._registerItem
。
_itemBuilder
負責建立可排序的 item
ReorderableDragStartListener
蒐集 Point Down Event
, 建立 recognizer
,呼叫 SliverReorderableListState.startDragItem
SliverReorderableListState
管理 _DragInfo, 建立 overlay
- register an item
- unregister an item
- make transition
classDiagram class SliverReorderableListState { - OverlayEntry _overlayEntry - DragInfo _dragInfo + startItemDragReorder() + cancelReorder() - _registerItem() - _unregisterItem() - _dragStart() - _dragUpdate() - _dragCancel() - _dropCompleted() - _dragReset() } class MultiDragGestureRecognizer { <<abstract>> } SliverReorderableListState -- _DragInfo : dashed SliverReorderableListState -- _EdgeDraggingAutoScrolller : dashed SliverReorderableListState -- OverlayEntry : dashed SliverReorderableListState -- MultiDragGestureRecognizer : dashed
_dragStart
建立 DragInfo
,並呼叫 DragInfo.startDrag
。
_dragInfo = _DragInfo(
item: item,
initialPosition: position,
scrollDirection: _scrollDirection,
onUpdate: _dragUpdate,
onCancel: _dragCancel,
onEnd: _dragEnd,
onDropCompleted: _dropCompleted,
proxyDecorator: widget.proxyDecorator,
tickerProvider: this,
);
_dragInfo!.startDrag();
對 items 裏面每一個 child 呼叫 updateForGap
。
for (final _ReorderableItemState childItem in _items.values) {
if (childItem == item || !childItem.mounted)
continue;
childItem.updateForGap(_insertIndex!, _dragInfo!.itemExtent, false, _reverse);
}
_dragUpdate
重新建立 overlayEntry
,並且呼叫 _dragUpdateItems
計算出新的 insert index,並且對所有子元素的 items 呼叫 updateForGap
。
if (newIndex != _insertIndex) {
_insertIndex = newIndex;
for (final _ReorderableItemState item in _items.values) {
if (item.index == _dragIndex! || !item.mounted)
continue;
item.updateForGap(newIndex, gapExtent, true, _reverse);
}
}
_ReorderableItem
classDiagram theme minty class SliverReorderableList class SliverReorderableListState class _ReorderableItem class _ReorderableItemState SliverReorderableList --> SliverReorderableListState SliverReorderableListState --> _ReorderableItem SliverReorderableListState --> _ReorderableItemState _ReorderableItem --> _ReorderableItemState
_ReorderableItem
在 SliverReorderableListState._itemBuilder
被使用,
SliverReorderableListState._itemBuilder
在 SliverReorderableListState.build
被呼叫。
需要設定 index, child 跟 themes。
const _ReorderableItem({
required Key key,
required this.index,
required this.child,
required this.capturedThemes,
}) : super(key: key);
_ReorderableItemState
_ReorderableItemState
是 ReorderItem
的狀態,主要用來更新 item 在 list 的 index。
class _ReorderableItemState {
- SliverReorderableListState _listState
+ updateForGap(int gapIndex, double gapExtent, bool animate, bool reverse)
}
class SliverReorderableListState {
- void _registerItem(_ReorderableItemState item)
- void _unregisterItem(_ReorderableItemState item)
}
_ReorderableItemState <-> SliverReorderableListState
classDiagram
class _ReorderableItemState
class _DragInfo
class OverlayEntry
class _DragItemProxy
_ReorderableItemState -- _DragInfo : dashed
_ReorderableItemState -- OverlayEntry : dashed
_ReorderableItemState -- _DragItemProxy : dashed
OverlayEntry --> _DragItemProxy
當父元件重新建立時,會觸發 ReorderableItemState.didUpdateWidget
,這時便會更新這個 child item 在所屬 list 的 index。
@override
void didUpdateWidget(covariant _ReorderableItem oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.index != widget.index) {
_listState._unregisterItem(oldWidget.index, this);
_listState._registerItem(this);
}
}
rebuild 實作方式很簡單,就是呼叫一次 setState 觸發重建
void rebuild() {
if (mounted) {
setState(() {});
}
}
InheritedTheme
InheritedTheme 是一個 Flutter 小部件,用於在子樹中 theme data。在這邊用來解決用來處理 overlay theme 的設定。
_itemBuilder() {
....
capturedThemes: InheritedTheme.capture(from: context, to: overlay.context),
}
在 _DragInfo.createProxy
用 CapturedThemes.wrap
渲染 theme,前面提過 _DragItemProxy
是用來建立 feedback
。 _dragStart
會找出是哪個 item 要被拖曳,然後為它建立 overlay 做 feedback _dragReset
還原狀態。
CustomScrollView
CustomScrollView
用於顯示可滾動的內容。它可以包含多個 Sliver
,這些 Sliver
可以組合在一起以創建各種滾動效果。 其中 builder
用於建立 list 中的 items,概念源碼如下:
CustomScrollView(
builder: (context, scrollOffset) {
return SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => ListTile(
title: Text('Item $index'),
),
childCount: 100,
),
);
},
)
IndexedSemantics
表示 items 是 indexed, 有排序的。
SliverChildBuilderDelegate
ItemBuilder 在這部分使用。因為一次建立所有的 item 很沒效率, 比較好的方式是 by demand 去做,在 Build 階段,只繪製 addRepaintBoundaries
、addSemanticIndexes
、addAutomaticKeepAlives
。
addRepaintBoundaries
addRepaintBoundaries
是一個可選屬性,預設為 true
。如果設置為 true
,則會為每個child添加一個 repaint 邊界。這意味著當child發生更改時,只會重繪該child,而不是整個滾動視圖。
addSemanticIndexes
addSemanticIndexes
是一個可選屬性,預設為 true
。如果設置為 true
,則會為每個child添加一個語義索引。這有助於輔助技術(例如屏幕閱讀器)理解滾動視圖的內容。
addAutomaticKeepAlives
addAutomaticKeepAlives
是一個可選屬性,預設為 false
。如果設置為 true
,則會為每個child添加一個自動 KeepAlive。這有助於防止滾動視圖在滾動時丟失child。
結論
1. List State 管理 Item
SliverReorderableListState
管理SliverReorderableList
中的 item。- 它負責跟踪 item 的位置、狀態等。
- 當 item 發生更改時,
SliverReorderableListState
會更新列表。
2. Item.didUpdateWidget 強制 Rebuild
- 當 item 的
widget
發生更改時,會調用item.didUpdateWidget()
。 - 在此方法中,您可以檢查
oldWidget
和newWidget
之間的差異,並根據需要更新 item。 - 在某些情況下,您可能需要強制 item 重建。您可以通過調用
setState()
方法來實現。
3. Overlay Entry 做 Feedback
OverlayEntry
可用於在 Flutter 應用程序中顯示浮動元素。- 在
SliverReorderableList
中,OverlayEntry
用於顯示拖動項目的反饋。 OverlayEntry
可以包含任何小部件,例如Text
、Image
或Container
。
4. 透過 InheritedTheme 解決 Overlay Theme
InheritedTheme
可用於在 Flutter 應用程序中傳遞主題數據。- 在
SliverReorderableList
中,InheritedTheme
用於解決OverlayEntry
的主題問題。 - 通過使用
InheritedTheme
,您可以確保OverlayEntry
始終使用正確的主題。
5. MultiDragGestureRecognizer 辨認何時觸發 Drag 跟其 Drag 的 Item
MultiDragGestureRecognizer
可用於識別多個拖動手勢。- 在
SliverReorderableList
中,MultiDragGestureRecognizer
用於識別何時開始拖動項目。 - 一旦開始拖動,
SliverReorderableList
會開始跟踪拖動項目的位置。