Extended NestedscrollView

  • Bug caused by Flutter extension NestedScrollView (1) Pinned header resolved
  • NestedScrollView (2) List scrolling synchronization resolved
  • The Flutter extension NestedScrollView (3) pull-down refresh solution

Why would you want to define a NestedScrollView yourself?

FlutterCandies QQ group: 181398081

Let’s start with the two issues I submitted:

1. The influence of the silver component Pinned to true on the scroll component in the body

2. When tabView is placed inside and the TAB is cached, the scrolling will affect each other

No progress has been made, using an expression to express the meaning of the Flutter team

But fortunately, there is source code, good I like to see the source code. This article is estimated to be a lot of length, please first buy a good melon seed soda sit in the front row, drive.

NestedScrollView is a complex component that is part of the Sliver family, with the CustomScrollView at the bottom.

There are many things in the Silver series. Let’s introduce them one by one.

CustomScrollView

The ancestor of the Sliver component, all slivers are in this one.

SliverList, which is a sliver that displays linear list of children.

SliverFixedExtentList, which is a more efficient sliver that displays linear list of children that have the same extent along the scroll axis. One more than SliverList equals the same row height. This will give you better performance

SliverPrototypeExtentList SliverPrototypeExtentList arranges its children in a line along the main axis starting at offset zero and without gaps. Each child is constrained to the same extent as the prototypeItem along the main axis and the SliverConstraints.crossAxisExtent along the cross axis.

SliverGrid, which is a sliver that displays a 2D array of children. You can set the number of rows per Grid

SliverPadding, which is a sliver that adds blank space around another sliver.

SliverPersistentHeader A sliver whose size varies when the sliver is scrolled to the leading edge of the viewport. This is the layout primitive that SliverAppBar uses for its shrinking/growing effect.

Very useful component, SliverAppBar is implemented with this. The feature of this component is that it can create Pinned elements that stay Pinned to the slide, which is a very handy build for the top components that people often use, I will use this to write a custom SliverAppbar later.

SliverAppBar, which is a sliver that displays a header that can expand and float as the scroll view scrolls.

SliverToBoxAdapter When you want to put a non-Sliver Widget inside a CustomScrollview, you need to wrap it around it.

SliverSafeArea A sliver that insets another sliver by sufficient padding to avoid intrusions by the operating system. For example, this will indent the sliver by enough to avoid the status bar at the top of the screen. To prevent various boundaries from being crossed, such as the status bar at the top

SliverFillRemaining sizes its child to fill the viewport in the cross axis and to fill the remaining space in the viewport in the main axis. If you use this it will fill up all the space in the remaining viewport

SliverOverlapAbsorber SliverOverlapAbsorberHandle the above two is the official specifically in order to solve our protagonist NestedScrollView today Pinned components affect the Body inside the Scroll state, But the authorities have been less than perfect.

Look at the source code is a fun thing, everyone with me to come. flutter\packages\flutter\lib\src\widgets\nested_scroll_view.dart

First, let’s look at the first question. You can see NestedScrollView from the Sample in the official documentation

DefaultTabController(
  length: _tabs.length, // This is the number of tabs.
  child: NestedScrollView(
    headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
      // These are the slivers that show up in the "outer" scroll view.
      return <Widget>[
        SliverOverlapAbsorber(
          // This widget takes the overlapping behavior of the SliverAppBar,
          // and redirects it to the SliverOverlapInjector below. If it is
          // missing, then it is possible for the nested "inner" scroll view
          // below to end up under the SliverAppBar even when the inner
          // scroll view thinks it has not been scrolled.
          // This is not necessary if the "headerSliverBuilder" only builds
          // widgets that do not overlap the next sliver.
          handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
          child: SliverAppBar(
            title: const Text('Books'), // This is the title in the app bar.
            pinned: true,
            expandedHeight: 150.0.// The "forceElevated" property causes the SliverAppBar to show
            // a shadow. The "innerBoxIsScrolled" parameter is true when the
            // inner scroll view is scrolled beyond its "zero" point, i.e.
            // when it appears to be scrolled below the SliverAppBar.
            // Without this, there are cases where the shadow would appear
            // or not appear inappropriately, because the SliverAppBar is
            // not actually aware of the precise position of the inner
            // scroll views.
            forceElevated: innerBoxIsScrolled,
            bottom: TabBar(
              // These are the widgets to put in each tab in the tab bar.
              tabs: _tabs.map((String name) => Tab(text: name)).toList(),
            ),
          ),
        ),
      ];
    },
    body: TabBarView(
      // These are the contents of the tab views, below the tabs.
      children: _tabs.map((String name) {
        return SafeArea(
          top: false,
          bottom: false,
          child: Builder(
            // This Builder is needed to provide a BuildContext that is "inside"
            // the NestedScrollView, so that sliverOverlapAbsorberHandleFor() can
            // find the NestedScrollView.
            builder: (BuildContext context) {
              return CustomScrollView(
                // The "controller" and "primary" members should be left
                // unset, so that the NestedScrollView can control this
                // inner scroll view.
                // If the "controller" property is set, then this scroll
                // view will not be associated with the NestedScrollView.
                // The PageStorageKey should be unique to this ScrollView;
                // it allows the list to remember its scroll position when
                // the tab view is not on the screen.
                key: PageStorageKey<String>(name),
                slivers: <Widget>[
                  SliverOverlapInjector(
                    // This is the flip side of the SliverOverlapAbsorber above.
                    handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
                  ),
                  SliverPadding(
                    padding: const EdgeInsets.all(8.0),
                    // In this example, the inner scroll view has
                    // fixed-height list items, hence the use of
                    // SliverFixedExtentList. However, one could use any
                    // sliver widget here, e.g. SliverList or SliverGrid.
                    sliver: SliverFixedExtentList(
                      // The items in this example are fixed to 48 pixels
                      // high. This matches the Material Design spec for
                      // ListTile widgets.
                      itemExtent: 48.0,
                      delegate: SliverChildBuilderDelegate(
                        (BuildContext context, int index) {
                          // This builder is called for each child.
                          // In this example, we just number each list item.
                          return ListTile(
                            title: Text('Item $index')); },// The childCount of the SliverChildBuilderDelegate
                        // specifies how many children this inner list
                        // has. In this example, each tab has a list of
                        // exactly 30 items, but this is arbitrary.
                        childCount: 30"" "" "" "" "" " },),); }).toList(), ), ), )Copy the code

It can be seen that the SliverOverlapAbsorber was used to wrap the SliverAppbar officially. In the body below, a SliverOverlapInjector was added on top of each list. The effect is that the height of the SliverOverlapInjector is equal to the Pinned height of the SliverAppbar. If you don’t add this code, when the list in the body scrolls down the SliverAppbar. The body can still scroll up, which means that the top scroll point of the body is 0 instead of the Pinned height of the SliverAppbar.

Why does this happen? It all starts with silver’s granddaddy, CustomScrollView. As many of you may have noticed, there is no Such thing as a ScrollController on these Sliver Widgets (except for CustomScrollview and NestedScrollView). When you put a Sliver widget in a CustomScrollView, the CustomScrollView handles all the Sliver Widgets. Each Sliver widget (the scrollable one) attaches its own ScrollPosition. For example, once the first list has rolled to the end, the second list will begin processing the corresponding ScrollPosition, render the element that appears in the viewport.

In our protagonist, NestedScrollView, we have two ScrollControllers.

class _NestedScrollController extends ScrollController {
  _NestedScrollController(
      this.coordinator, {
        double initialScrollOffset = 0.0.String debugLabel,
Copy the code

An inner and an outer. The Outer is responsible for the scrolling widgets in the headerSliverBuilder and the inner is responsible for the scrolling widgets in the body and when the Outer gets to the end of the scroll, it looks to see if there’s anything in the inner that can scroll and starts scrolling.

In order to solve problem 1, we need to deal with _NestedScrollPosition controlled by ScrollController Outer. Problem 1 is the extent that outer can scroll when there are pinned widgets in the header. This pinned total height should be subtracted. This is done when scrolling under pinned components. We’ll start rolling the inner.

Inside the _NestedScrollPosition

// The _NestedScrollPosition is used by both the inner and outer viewports of a
// NestedScrollView. It tracks the offset to use for those viewports, and knows
// about the _NestedScrollCoordinator, so that when activities are triggered on
// this class, they can defer, or be influenced by, the coordinator.
class _NestedScrollPosition extends ScrollPosition
    implements ScrollActivityDelegate {
  _NestedScrollPosition({
    @required ScrollPhysics physics,
    @required ScrollContext context,
    double initialPixels = 0.0,
    ScrollPosition oldPosition,
    String debugLabel,
    @required this.coordinator,
  }) : super(
Copy the code

I override the applyContentDimensions method

 @override
  bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) {
    if (debugLabel == 'outer'&& coordinator.pinnedHeaderSliverHeightBuilder ! =null) {
      maxScrollExtent =
          maxScrollExtent - coordinator.pinnedHeaderSliverHeightBuilder();
      maxScrollExtent = math.max(0.0, maxScrollExtent);
    }
    return super.applyContentDimensions(minScrollExtent, maxScrollExtent);
  }
Copy the code

PinnedHeaderSliverHeightBuilder is I was used to get passed in the outermost layers from Pinned to true all silver header height.. Here we subtracted the maximum outer scroll extent from the Pinned total height, thus we solved the problem perfectly.1

Sample code

In my demo. The height is pinned to the status bar + Appbar + 1 or 2 Tabbars. Why do I use a function here instead of just passing a height that’s good? Because in my case, the pinned height will change.

 var tabBarHeight = primaryTabBar.preferredSize.height;
    var pinnedHeaderHeight =
        //statusBa height
        statusBarHeight +
            //pinned SliverAppBar height in header
            kToolbarHeight +
            //pinned tabbar height in header
            (primaryTC.index == 0 ? tabBarHeight * 2 : tabBarHeight);
    return NestedScrollViewRefreshIndicator(
      onRefresh: onRefresh,
      child: extended.NestedScrollView(
        headerSliverBuilder: (c, f) {
          return _buildSliverHeader(primaryTabBar);
        },
        //
        pinnedHeaderSliverHeightBuilder: () {
          return pinnedHeaderHeight;
        },
Copy the code

Finally, put Github extended_nested_scroll_view on it. If you have a better way to solve this problem or you don’t understand anything, please let me know. Thank you.