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 fromBouncingScrollPhysics
和 NotificationListener
Because theBouncingScrollPhysics
To 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, orScrollUpdateNotification
Well, make it, or rewrite itBouncingScrollPhysics
.
This is what most LoadingMore doStack
andNotificationListener
Implement the refreshed component. But here, instead of using either of these methods, it’s similarSliverPersistentHeaderDelegate
The kind of pass to developersbuild
Method, and provide similar likeshrinkOffset
.overlapsContent
To build the corresponding parameterSliver
.
SliverPersistentHeader
The secret of
Top effect of the father, the content is not much to say, because it has not used thisWidget
I can’t read the one behind.
No more talking, no more talking _SliverPersistentHeaderElement
Looking at the source code, we can see that thiselement
和 renderobj
They hold each other.
First take a look atelement
updatewidget
Method:
// 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 torenderobj
Inside, theelement
To create therenderobj
An important parent class ofRenderSliverPersistentHeader
And then the succession, according toWidget
的 pinned
和 floating
Property generates the corresponding private_RenderSliverXXXX
(we don’t care), and with_RenderSliverPersistentHeaderForWidgetsMixin
(That’s the point).
This mixin is about lettingrenderobj
holdelement
Cause, and by callingupdateChild
( renderobj
Method) triggerdelegate
To 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 triggerbuild
What 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 suctionRenderSliverPersistentHeader
forperformLayout
, 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 similaritydelegate
和 build
You have to know the internal implementation here, but let’s get into itTo load more
The content of the
SliverConstraints
和 SliverGeometry
The magic of
ViewPort
It’s clever. It’s always rightvisible
和 The buffer
Inside all slivers to proceedperformLayout
.SliverConstraints
The input to theSliver
The layout,SliverGeometry
Output toViewPort
.
We can use these properties to judge whether we slip to the bottom of the pinch, so as to achieve nothingScrollNotification
forLoadingMore
!!!!!
SliverConstraints
Attribute is introduced
-
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).
-
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.
-
ViewportMainAxisExtent: as its name suggests.
-
Overlap: paintextent-layoutextent of a previous Sliver, commonly used for effects such as top drawing, official notes written in detail and rarely used here.
SliverGeometry
Attribute is introduced
-
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.
-
PaintExtent: as the name suggests.
-
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 myselfperformLayout
code
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 elegantbuild
I accidentally read the code associated with loadingCupertinoSliverRefreshControl
The source code.
The mage isCupertinoSliverRefreshControl
The 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 creationdelegate
Set up the necessary properties, put in the code for the state machine, such as thresholds that trigger updates, and remember that you are performing asynchronysetState
When the frame should be delayed or brought forwardbuild
Previously, 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