Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”

This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.

preface

The last article on the ListView to do a basic analysis, sort out the next ListView is composed of what things;

So now you’ve got your drill, your hammer, your screwdriver, and it’s time to disassemble; So first of all, let’s start with the dismantling of the outer shell;

directory

Juejin. Cn/post / 701653…

First things first

First of all, the PrimaryScroollController described above is only the ScrollerController provided by default. If a custom ScrollerController is provided, So custom Controller will replace PrimaryScroollController;

In the build method, it says:

final ScrollController? scrollController =
    primary ? PrimaryScrollController.of(context) : controller;
Copy the code

Here, start with PrimaryScrollController; After all, it is essentially an InheritedWidget, and finally provides a ScrollController for sharing. Compared with the ordinary ScrollController, it contains one more layer and has no great impact on the analysis process.

Conceptual analysis of ScrollController and its involved parts

First locate the location where PrimaryScrollController first appears, ScrollView build method:

@override Widget build(BuildContext context) { final List<Widget> slivers = buildSlivers(context); final AxisDirection axisDirection = getDirection(context); final ScrollController? scrollController = primary ? PrimaryScrollController.of(context) : controller; final Scrollable scrollable = Scrollable( dragStartBehavior: dragStartBehavior, axisDirection: axisDirection, controller: scrollController, physics: physics, scrollBehavior: scrollBehavior, semanticChildCount: semanticChildCount, restorationId: restorationId, viewportBuilder: (BuildContext context, ViewportOffset offset) { return buildViewport(context, offset, axisDirection, slivers); }); final Widget scrollableResult = primary && scrollController ! = null ? PrimaryScrollController.none(child: scrollable) : scrollable; if (keyboardDismissBehavior == ScrollViewKeyboardDismissBehavior.onDrag) { return NotificationListener<ScrollUpdateNotification>( child: scrollableResult, onNotification: (ScrollUpdateNotification notification) { final FocusScopeNode focusScope = FocusScope.of(context); if (notification.dragDetails ! = null && focusScope.hasFocus) { focusScope.unfocus(); } return false; }); } else { return scrollableResult; }}Copy the code

From the perspective of the Build method, there are three things to understand:

  1. buildSlivers
  2. ScrollController
  3. Scrollable

buildSlivers

This is stated in the source code:

/// Build the list of widgets to place inside the viewport.
///
/// Subclasses should override this method to build the slivers for the inside
/// of the viewport.
@protected
List<Widget> buildSlivers(BuildContext context);
Copy the code

Provide a list of widgets wrapped in a ViewPort. In plain English, provide a list of subviews of the ListView;

What about this new ViewPort? Just in the list of contents, then put it aside ~~ then go to understand the content of this part;

ScrollController

Coming to the topic of this post, as usual, let’s look at how ScrollController is defined in the comments:

Controls a scrollable widget. Scroll controllers are typically stored as member variables in [State] objects and are reused in each [State.build]. A single scroll controller can be used to control multiple scrollable widgets, but some operations, such as reading the scroll [offset], require the controller to be used with a single scrollable widget. A scroll controller creates a [ScrollPosition] to manage the state specific to an individual [Scrollable] widget. To use a custom [ScrollPosition], subclass [ScrollController] and override [createScrollPosition]. A [ScrollController] is a [Listenable]. It notifies its  listeners whenever any of the attached [ScrollPosition]s notify _their_ listeners (i.e. whenever any of them scroll). It does not notify its listeners when the list of attached [ScrollPosition]s changes. Typically used with [ListView], [GridView], [CustomScrollView].Copy the code

Translation in a word:

A ScrollController is essentially a Listenable that notifies its listeners when the list is scrolling; The management of sliding components is realized by constructing ScrollPosition and providing it to Scrollable. It lives throughout the Widget’s life cycle and is reused throughout the build process.

Well, that single paragraph throws up a few more nouns:

  1. ScrollPosition
  2. Listenable

Let’s tackle the noun part first, at least not so much as understand what it says:

ScrollPosition

 Determines which portion of the content is visible in a scroll view.

 The [pixels] value determines the scroll offset that the scroll view uses to
 select which part of its content to display. As the user scrolls the
 viewport, this value changes, which changes the content that is displayed.

 The [ScrollPosition] applies [physics] to scrolling, and stores the
 [minScrollExtent] and [maxScrollExtent].

 Scrolling is controlled by the current [activity], which is set by
 [beginActivity]. [ScrollPosition] itself does not start any activities.
 Instead, concrete subclasses, such as [ScrollPositionWithSingleContext],
 typically start activities in response to user input or instructions from a
 [ScrollController].

 This object is a [Listenable] that notifies its listeners when [pixels]
 changes.

 ## Subclassing ScrollPosition

 Over time, a [Scrollable] might have many different [ScrollPosition]
 objects. For example, if [Scrollable.physics] changes type, [Scrollable]
 creates a new [ScrollPosition] with the new physics. To transfer state from
 the old instance to the new instance, subclasses implement [absorb]. See
 [absorb] for more details.

 Subclasses also need to call [didUpdateScrollDirection] whenever
 [userScrollDirection] changes values.

Copy the code

Translation in a word:

ScrollPosition is essentially a Listenable that notifies its listeners when the list is scrolling; A visual child widget that manages a scrolling widget. In it, there are pixels, minScrollExtent and maxScrollExtent, which constantly change with scrolling. The physics effect is handled by something called activity, which is probably an analog calculator that calculates inertia or something like that, and the simulator is made up of a concrete subclass of ScrollPosition, for example;

It looks like the ViewPort function is similar to the previous guess… But considering that ScrollPosition is essentially a Listenable, it is assumed that it is only used as a place to store data, calculate data, and finally notify other people of their work, and the ViewPort will change the display effect based on the data here. So let’s check this out later

Listenable

An object that maintains a list of listeners. The listeners are typically used to notify clients that the object has been updated. There are two variants of this interface: * [ValueListenable], an interface that augments the [Listenable] interface with the concept of a _current value_. * [Animation], an interface that augments the [ValueListenable] interface to add the concept of direction (forward or reverse). Many classes in the Flutter API use or implement these interfaces. The following subclasses are especially relevant: * [ChangeNotifier], which can be subclassed or mixed in to create objects that implement the [Listenable] interface. * [ValueNotifier], which implements the [ValueListenable] interface with a mutable value that triggers the notifications when modified. The  terms "notify clients", "send notifications", "trigger notifications", and "fire notifications" are used interchangeably.Copy the code

According to the comments, this is just a normal listener that provides notification;

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.Copy the code

A Scrollable, like a Container, is a composite Widget;

The build method is used to assign _ScrollableScope(an InheritedWidget, Use to share the above ScrollPosition as well as the State of the Scrollable itself), RawGestureDetector, and IgnorePointer to enable or disable the corresponding functions. Provide data calculation and other functions;

How does ScrollController work

So with a brief overview of the basic concepts, let’s look at how ScrollController works;

Start with the build method and analyze how ListView is driven

In the build method of ScrollableState, apart from the other bits and pieces, this is the combination:

Widget result = _ScrollableScope( scrollable: this, position: position, // TODO(ianh): Having all these global keys is sad. child: Listener( onPointerSignal: _receivedPointerSignal, child: RawGestureDetector( key: _gestureDetectorKey, gestures: _gestureRecognizers, behavior: HitTestBehavior.opaque, excludeFromSemantics: widget.excludeFromSemantics, child: Semantics( explicitChildNodes: ! widget.excludeFromSemantics, child: IgnorePointer( key: _ignorePointerKey, ignoring: _shouldIgnorePointer, ignoringSemantics: false, child: widget.viewportBuilder(context, position), ), ), ), ), );Copy the code

1. First, position and ScrollableState itself are shared through a _ScrollableScope; So far, this part is not involved in the workflow of ScrollController, so ignore it for now…

Note: However, there is something interesting about ScrollPosition when you check where it is called: there is a method called ShameDeferredLoading in ScrollPosition. If true is returned, the scrolling is too fast and expensive operations that affect the UI should be postponed ………… This method is a sliding control of Flutter, which is a way to load pictures in the slide. Maybe we could add something like a queue on top of that, synthesize the return values, and implement a lazy-loaded component?

The following is a combination of Listener + RawGestureDetector;

A PointerSignal is a non-continuous event message that does not start with a down binding, such as a hold or drag. Add a Listener to enable the result of the event to be processed.

After that, the RawGestureDetector

The RawGestureDetector here sets gestures as the trigger for driver events calculation; You can see it initialized in the setCanDrag method and bound to various methods such as _handleDragDown;

The setCanDrag method will fire the applyViewportDimension of ScrollPosition on the first ViewPort layout and change a Boolean value to true indicating that the Dimension has changed. And then when it’s time to trigger applyContentDimensions, the applyNewDimensions method is called, In the specific implementation class – ScrollPositionWithSingleContext ScrollPostion, rewrite the applyNewDimensions method, and calls the setCanDrag, implement initialization; Since then, gesture monitor and corresponding trigger calculation function officially online;

Gestures, including _handleDragDown and _hold and _drag methods, are essentially a calculator connected to ScrollPosition and its corresponding implementation of the simulator Activity. And according to the gesture data, call activity to calculate the data and set to Position;

How does a ViewPort bind a ScrollPosition to a ViewPort? How does a ViewPort update a ScrollPosition? Wait until the ViewPort part is parsed;

/ / Use an IgnorePointer to intercept events that are distributed downward based on gestures. For example, if you’re dragging a listView, you don’t need to pass events down.

In the build method, the ListView driver is not complicated. The gesture listener passes events to Position to calculate, update, draw, etc.

So ScrollController doesn’t really make sense, does it? I think all I need is a ScrollPosition?

According to the annotation definition of ScrollController above, its participation process is also realized by constructing and providing Position. Even in the build method, Position exists in the brush. So it sounds like a ScrollController doesn’t really exist… Even though it contains a comment section of just over 200 lines, it doesn’t seem to have any effect ?????

Can I delete this and just provide a position??

In the ListView, the ScrollController really doesn’t seem to exist. In the ScrollableState, it just creates a position and attach and detach.

The position of the ScrollController is stored as a List; That is, it is possible to bind multiple positions; If you look at the inheritance, there’s something in ScrollController like _NestedScrollController that’s written specifically to deal with positions;

It seems that the basic function of ScrollController is to manage position uniformly and provide unified binding, binding and unbinding notification and other functions. To look at the core logic, again focus on ScrollPosition;

Maybe the title of this post should be ScrollPosition and Scrollable?

Lazy people who write too long to read sum up

The overall core is ScrollerPostion;

Scrollable constructs a gesture listener and notifies the result of gesture listener to ScrollPosition for its calculation and notification display.

The function of ScrollController is to provide unified registration, binding notification and other functions for ScrollPosition. The core functionality is ScrollPosition; (Just for ListView, if you’re looking at something like NestedScrollerView, it’s not that simple.)

The function of ScrollPosition is to calculate the result of gesture operation through a bunch of built-in simulators, calculators, etc., and inform ViewPort and other parts of the re-measurement and re-drawing, etc.