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

_ReorderableItemSliverReorderableListState._itemBuilder 被使用, SliverReorderableListState._itemBuilderSliverReorderableListState.build 被呼叫。

需要設定 index, child 跟 themes。

  const _ReorderableItem({
    required Key key,
    required this.index,
    required this.child,
    required this.capturedThemes,
  }) : super(key: key);

_ReorderableItemState

_ReorderableItemStateReorderItem 的狀態,主要用來更新 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.createProxyCapturedThemes.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 階段,只繪製 addRepaintBoundariesaddSemanticIndexesaddAutomaticKeepAlives

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 可以包含任何小部件,例如 TextImage 或 Container

4. 透過 InheritedTheme 解決 Overlay Theme

  • InheritedTheme 可用於在 Flutter 應用程序中傳遞主題數據。
  • 在 SliverReorderableList 中,InheritedTheme 用於解決 OverlayEntry 的主題問題。
  • 通過使用 InheritedTheme,您可以確保 OverlayEntry 始終使用正確的主題。

5. MultiDragGestureRecognizer 辨認何時觸發 Drag 跟其 Drag 的 Item

  • MultiDragGestureRecognizer 可用於識別多個拖動手勢。
  • 在 SliverReorderableList 中,MultiDragGestureRecognizer 用於識別何時開始拖動項目。
  • 一旦開始拖動,SliverReorderableList 會開始跟踪拖動項目的位置。

See Also