LoadingMore and Sliver are combined

If you are new to Flutter and looking for a solution, then this article is not for you. If it is an old 🐦, that point a praise or comment area casually speak two sentences, let me know that big guy saw my article 👀.

Get into the business

Arises fromBouncingScrollPhysicsNotificationListenerBecause theBouncingScrollPhysicsTo achieve the effect of rebound, the value of apply is 0, resulting in XXX not being emittedOverScrolledNotification, in order to obtain the information of overscrolled, orScrollUpdateNotificationWell, make it, or rewrite itBouncingScrollPhysics.

This is what most LoadingMore doStackandNotificationListenerImplement the refreshed component. But here, instead of using either of these methods, it’s similarSliverPersistentHeaderDelegateThe kind of pass to developersbuildMethod, and provide similar likeshrinkOffset.overlapsContentTo build the corresponding parameterSliver.

SliverPersistentHeaderThe secret of

Top effect of the father, the content is not much to say, because it has not used thisWidgetI can’t read the one behind.

No more talking, no more talking _SliverPersistentHeaderElementLooking at the source code, we can see that thiselementrenderobjThey hold each other.

First take a look atelementupdatewidgetMethod:

  // Element triggers the delegate.build method
  void _build(double shrinkOffset, booloverlapsContent) { owner! .buildScope(this, () {
     child = updateChild(
       child,
       floating
         ? _FloatingHeader(child: widget.delegate.build(
           this,
           shrinkOffset,
           overlapsContent
         ))
         : widget.delegate.build(this, shrinkOffset, overlapsContent),
       null,); }); }Copy the code

And let’s see what that corresponds torenderobjInside, theelementTo create therenderobjAn important parent class ofRenderSliverPersistentHeaderAnd then the succession, according toWidgetpinnedfloatingProperty generates the corresponding private_RenderSliverXXXX(we don’t care), and with_RenderSliverPersistentHeaderForWidgetsMixin(That’s the point).

This mixin is about lettingrenderobjholdelementCause, and by callingupdateChild ( renderobjMethod) triggerdelegateTo provide thebuild

mixin _RenderSliverPersistentHeaderForWidgetsMixin on RenderSliverPersistentHeader {
  _SliverPersistentHeaderElement? _element;

  @override
  double getminExtent => _element! .widget.delegate.minExtent;@override
  double getmaxExtent => _element! .widget.delegate.maxExtent;@override
  void updateChild(double shrinkOffset, bool overlapsContent) {
    assert(_element ! =null); _element! ._build(shrinkOffset, overlapsContent); }@protected
  voidtriggerRebuild() { markNeedsLayout(); }}Copy the code

So the whole triggerbuildWhat does the process look like? Here is a review of the Flutter sliding part for you

Recommended articles:

  • Different angles take you through the sliding list implementation of Flutter -GSY

  • Full description of Flutter development (13, comprehensive in-depth touch and slide principle)-GSY

  • How does Flutter achieve list sliding -Nayuta

I’ll hide the magic. It’s likeScrollable -> Viewport flush -> Sliver performLayout.

For example, now it’s the top suctionRenderSliverPersistentHeaderforperformLayout, see the source code that is executedlayoutChild(Trigger suction top) andupdateGeometry(should be the top effect, because of the updated spindle coordinates).

layoutChild(Clouds see mist)

InvokeLayoutCallback performed updateChild, also is our one method, the above _RenderSliverPersistentHeaderForWidgetsMixin triggered the delegate of the build, Finally, conduct the layout.

  void layoutChild(double scrollOffset, double maxExtent, { bool overlapsContent = false{})final double shrinkOffset = math.min(scrollOffset, maxExtent);
    if(_needsUpdateChild || _lastShrinkOffset ! = shrinkOffset || _lastOverlapsContent ! = overlapsContent) { invokeLayoutCallback<SliverConstraints>((SliverConstraints constraints) {RenderObj updateChild triggers Element's _build
        updateChild(shrinkOffset, overlapsContent);
      });
      _lastShrinkOffset = shrinkOffset;
      _lastOverlapsContent = overlapsContent;
      _needsUpdateChild = false;
    }

    double stretchOffset = 0.0;
    if(stretchConfiguration ! =null && constraints.scrollOffset == 0.0) { stretchOffset += constraints.overlap.abs(); } child? .layout( constraints.asBoxConstraints( maxExtent: math.max(minExtent, maxExtent - shrinkOffset) + stretchOffset, ), parentUsesSize:true,); . }Copy the code

Some people ask, hey, you this suction top and LoadingMore what relationship?

It doesn’t really matter, but it doesn’t matter, because we want to achieve that kind of similaritydelegatebuildYou have to know the internal implementation here, but let’s get into itTo load moreThe content of the

SliverConstraintsSliverGeometryThe magic of

ViewPortIt’s clever. It’s always rightvisibleThe bufferInside all slivers to proceedperformLayout.SliverConstraintsThe input to theSliverThe layout,SliverGeometryOutput toViewPort.

We can use these properties to judge whether we slip to the bottom of the pinch, so as to achieve nothingScrollNotificationforLoadingMore!!!!!

SliverConstraintsAttribute is introduced

  1. RemainingPaintExtent: can simply be understood as how many more pixels can be painted (not necessarily… But make sure that when your sliver slides out of the bottom, this value is zero).

  2. Whoreingscrollextent: The slide size consumed by the previous Sliver (corresponding to scrollExtent below) can be used to determine whether the Sliver is full of viewports.

  3. ViewportMainAxisExtent: as its name suggests.

  4. Overlap: paintextent-layoutextent of a previous Sliver, commonly used for effects such as top drawing, official notes written in detail and rarely used here.

SliverGeometryAttribute is introduced

  1. ScrollExtent: The length of the slider, let’s say the last sliver, the height is 100, but this value is 0, so you can only see it when you’re doing the overscroll, and then after you let go and bounce it off the bottom of the viewport.

  2. PaintExtent: as the name suggests.

  3. LayoutExtent & maxPaintExtent: As the name suggests, the drawing screen layout information that opens DevTools corresponds to the arrow ➡️.

Attributes are given to you, how to judge I do not need to say more, right? I wrote the last one myselfperformLayoutcode

  double overscrolled = 0;

  double getmaxScrollExtent => _element! .widget.delegate.maxScrollExtent;@override
  void performLayout() {
    SliverConstraints constraints = this.constraints;
    // We never call build unless user overscrolled.
    if (constraints.remainingPaintExtent < 1) {
      geometry = SliverGeometry.zero;
      return;
    }

    // calculate remain space on viewport.
    // if slivers before this one not fill the viewportExtent, this value could
    // be < 0, which means this sliver is always visiable now, in this case, we
    // never performm any load more behavior.
    double extent =
        constraints.precedingScrollExtent - constraints.viewportMainAxisExtent;

    // the total overscrolled area in viewport.
    double maxExtent = constraints.remainingPaintExtent - min(constraints.overlap, 0.0);

    if (extent <= 0) {
      // we offer overscrolled 0 to builder, but the constraint to passed to
      // child still the remainingPaintExtent. you can use this constraint
      //to custom what you want.
      overscrolled = 0; invokeLayoutCallback((constraints) { updateChild(); }); child? .layout(constraints.asBoxConstraints(maxExtent: maxExtent)); geometry = SliverGeometry( scrollExtent:0,
        paintExtent: maxExtent,
        maxPaintExtent: maxExtent,
      );
      return;
    }

    // here, remainingPaintExtent is overscrolled.overscrolled = maxExtent; invokeLayoutCallback((constraints) { updateChild(); }); child? .layout(constraints.asBoxConstraints(maxExtent: maxExtent), parentUsesSize:true);

    geometry = SliverGeometry(
        scrollExtent: min(maxExtent, maxScrollExtent),
        paintExtent: maxExtent,
        maxPaintExtent: maxExtent);
  }

Copy the code

Look at the renderings

We need a mage to trigger the update

When I can’t figure out how to write an elegantbuildI accidentally read the code associated with loadingCupertinoSliverRefreshControlThe source code.

The mage isCupertinoSliverRefreshControlThe state of the machine

Unfortunately, you can’t read the official state machine code, because it’s for drawing and rubbing, so I did it myself and posted it here.

Then, in their own creationdelegateSet up the necessary properties, put in the code for the state machine, such as thresholds that trigger updates, and remember that you are performing asynchronysetStateWhen the frame should be delayed or brought forwardbuildPreviously, the postponement method was used.

  ValueNotifier<RefreshState> loadingStateNotifier;

  bool isTriggered = false;

  bool canTiggerNext = false;

  @override
  final double triggerDistance;

  @override
  final double maxScrollExtent;

  final double ignoreRefreshDistance;

  RefreshCallback? onRefresh;

  @override
  Widget builder(BuildContext context, double overscrolled) {
    handleNextState(loadingStateNotifier, overscrolled);
    return ValueListenableBuilder<RefreshState>(
        valueListenable: loadingStateNotifier,
        builder: (context, state, child) {
          return handleStateBuild(state, overscrolled);
        });
  }

  // State machine code
  void handleNextState(ValueNotifier<RefreshState> currentState, double overscrolled) {
    switch (currentState.value) {
      case RefreshState.inactive:
        if (overscrolled < triggerDistance) {
          currentState.value = RefreshState.inactive;
          break;
        }

        isTriggered = true; currentState.value = RefreshState.refreshing; SchedulerBinding.instance! .addPostFrameCallback((timeStamp) { onRefresh! ().whenComplete( () { isTriggered =false; currentState.value = RefreshState.done; }); });break;
      case RefreshState.refreshing:
        if (isTriggered) {
          currentState.value = RefreshState.refreshing;
          break;
        }
        currentState.value = RefreshState.done;
        break;
      case RefreshState.done:
        if (overscrolled < ignoreRefreshDistance) {
          // when done, wating overscroll to 0 or user make it to 0,
          // the state could be inactive, otherwise, keep
          currentState.value = RefreshState.inactive;
          break;
        }
        currentState.value = RefreshState.done;
    }
  }

  Widget handleStateBuild(RefreshState currentState, double overscrolled) {
    switch (currentState) {
      case RefreshState.inactive:
        // Widgets to build for Inactive
      case RefreshState.refreshing:
        // refreshing needs to build the widget
      case RefreshState.done:
        // done The widget that needs to be built}}Copy the code

Realize the effect drawing

The use method is very elegant, similar to Pinterest update effect

return  CustomScrollView(
          slivers: [
            // Suppose here is the method's SliverWaterFallFlow....
            LoadingMoreSliver(
              onRefresh: () async {
                await getDataFromServers();
                setState((){
                // Data update); },)],);Copy the code

summary

The code is not yet sound enough, and for the sake of laziness, I didn’t set up a similar oneoverlapProperty of, but whenSliverCan not fill a page, the default return 0, later to write again (recently busy looking for internship, no time to write), the source code will be in the next month when I understand the design pattern or demo of this APP perfect, posted in the personal warehouse, or there are big guys want to help improve.