Flutter Scrollable Widget Class 源碼解析
這篇將著重描述 Flutter 中滾動機制中,怎麼對應 Drag
狀態發送 ScrollNotification
。
這裡不清楚 Scrollable
怎麼跟 ScrollActivity
互動,唯一知道的是 ScrollActivity
判斷要不要發送 ScrollUpdateNotification
。
Scrollable
Scrollable
很少會直接被使用。
/// A widget that scrolls.
///
/// [Scrollable] implements the interaction model for a scrollable widget,
/// including gesture recognition, but does not have an opinion about how the
/// viewport, which actually displays the children, is constructed.
///
/// It's rare to construct a [Scrollable] directly. Instead, consider [ListView]
/// or [GridView], which combine scrolling, viewporting, and a layout model. To
/// combine layout models (or to use a custom layout mode), consider using
/// [CustomScrollView].
///
/// The static [Scrollable.of] and [Scrollable.ensureVisible] functions are
/// often used to interact with the [Scrollable] widget inside a [ListView] or
/// a [GridView].
///
/// To further customize scrolling behavior with a [Scrollable]:
///
/// 1. You can provide a [viewportBuilder] to customize the child model. For
/// example, [SingleChildScrollView] uses a viewport that displays a single
/// box child whereas [CustomScrollView] uses a [Viewport] or a
/// [ShrinkWrappingViewport], both of which display a list of slivers.
///
/// 2. You can provide a custom [ScrollController] that creates a custom
/// [ScrollPosition] subclass. For example, [PageView] uses a
/// [PageController], which creates a page-oriented scroll position subclass
/// that keeps the same page visible when the [Scrollable] resizes.
整理類圖如下:
classDiagram class Scrollable class ScrollableState class _ScrollableScope class ScrollNotification
ScrollableState.setCanDrag
被呼叫時,便會把 drag event handler 綁定到 GestureRecognizer
。如下面程式碼所示。
..onDown = _handleDragDown
..onStart = _handleDragStart
..onUpdate = _handleDragUpdate
..onEnd = _handleDragEnd
..onCancel = _handleDragCancel
這時 _handleDragStart
會負責處理 Drag 的狀態跟 Scroll 狀態的對應關係,因為不是每一個 drag 都是 scrolling,所以要判對是不是在滾動當前的 widget,
如果判斷是滾動,則讓 ScrollDragController
去發送對應的 Scroll
事件,讓 ScrollView
建立的 NotificationListener
可以收到,並做對應的事件處理。
@override
Widget build(BuildContext context) {
assert(_position != null);
// _ScrollableScope must be placed above the BuildContext returned by notificationContext
// so that we can get this ScrollableState by doing the following:
//
// ScrollNotification notification;
// Scrollable.of(notification.context)
//
// Since notificationContext is pointing to _gestureDetectorKey.context, _ScrollableScope
// must be placed above the widget using it: RawGestureDetector
Widget result = _ScrollableScope(
_handleDragStart()
使用 ScrollPosition.darg
。
void _handleDragStart(DragStartDetails details) {
// It's possible for _hold to become null between _handleDragDown and
// _handleDragStart, for example if some user code calls jumpTo or otherwise
// triggers a new activity to begin.
assert(_drag == null);
_drag = position.drag(details, _disposeDrag);
assert(_drag != null);
assert(_hold == null);
}
ScrollPosition.drag
是抽象方法 ,這裡以 ScrollPositionWithSingleContext.darg
實作解釋:
@override
Drag drag(DragStartDetails details, VoidCallback dragCancelCallback) {
final ScrollDragController drag = ScrollDragController(
delegate: this,
details: details,
onDragCanceled: dragCancelCallback,
carriedVelocity: physics.carriedMomentum(_heldPreviousVelocity),
motionStartDistanceThreshold: physics.dragStartDistanceMotionThreshold,
);
beginActivity(DragScrollActivity(this, drag));
assert(_currentDrag == null);
_currentDrag = drag;
return drag;
}
所以 drag 的生命週期是由 ScrollDragController
控制。
ScrollDragController
classDiagram class Drag { <<interface>> } class ScrollActivity { <<abstract>> } class ScrollNotification { <<abstract>> } class ScrollActivityDelegate { <<interface>> } class ScrollDragController class DragScrollActivity ScrollActivity <|-- DragScrollActivity Drag <|-- ScrollDragController ScrollActivity --> ScrollActivityDelegate DragScrollActivity --> ScrollDragController DragScrollActivity --> ScrollNotification
classDiagram class Drag { <<abstract>> } class GestureRecognizer { <<abstract>> } class PanView { <<abstract>> } class CustomPanView { <<abstract>> } class Panable class PanableState class PanController class PanMoveController class PanActivity { <<abstract>> } class DragPanActivity class PanPosition { <<abstract>> } class Viewport { <<abstract>> } class ViewportOffset { <<abstract>> } class PanNotification { <<abstract>> } ViewportOffset <|-- PanPosition Drag <|-- PanDragController PanActivity <|-- DragPanActivity PanView <|-- CustomPanView CustomPanView --> Panable : 建立 Panable --> PanableState : 建立 PanableState --> NotificationListener : 建立 NotificationListener --> PanNotification : 處理 PanController --> PanPosition : 建立 PanableState --> PanPosition : 使用 PanableState --> PanMoveController : 建立 PanableState --> PanController : 使用 PanableState --> Viewport : 建立 Viewport --> ViewportOffset PanMoveController --> PanMoveActivity : 建立 PanActivity --> PanNotification : 發送 PanableState --> GestureRecognizer : 綁定 handler GestureRecognizer --> Drag : 建立
ScrollActivityDelegate
實作 ScrollActivityDelegate
介面的類別有好幾個,但主要實作的是 ScrollPositionWithSingleContext
,定義在 ~src/widgets/scroll_position_with_single_context.dart~
。 ScrollPositionWithSingleContext
會用 ScrollContext
管理當前 scrolling 的狀態,其實就是 "Scrollable" widget 的 State object。 記住,很多時候,我們需要追蹤多個 scrolling
的狀態,而這個 delegator 只處理 single context 的情境。
類圖整理如下:
classDiagram class ScrollActivityDelegate { <<interface>> } class ScrollPositionWithSingleContext { <<interface>> } class ScrollContext { <<interface>> } ScrollActivityDelegate <|-- ScrollPositionWithSingleContext ScrollPositionWithSingleContext --> ScrollContext ScrollPositionWithSingleContext --> IdleScrollActivity ScrollPositionWithSingleContext --> DragScrollActivity ScrollPositionWithSingleContext --> HoldScrollActivity ScrollContext <|-- ScrollableState
ScrollActivity
ScrollActivity
是一個抽象類別,分別對應不同的滾動活動,子類別如下:
- BallisticScrollActivity
- HoldScrollActivity
- DragScrollActivity
- DrivenScrollActivity
- IdleScrollActivity
其中 IdleScrollActivity
是初始狀態,在 drag 開始時,才進入 DragScrollActivity
。