preface
I have been in the pit of the Flutter for a year, and my exposure to the Flutter is just the tip of the iceberg. Many things may know how to use the Flutter, but I do not quite understand the principle of it. As the saying goes, only when you go deep can you shallow out. In this series, we’ll look at the silver-related source code one by one in the hope that we can learn from each other and not be afraid of Sliver.After reading Flutter Sliver’s Enemies for Life you will not be afraid to use Sliver and Sliver will be the love of your life. Welcome to Flutter CandiesQQ group: 181398081.
- Flutter Silver’s Lifetime Enemy (ScrollView)
- Flutter silver lifetime enemies (ExtendedList)
- Flutter Sliver You want the waterfall flow small XX
- Flutter silver locks your beauty
Below are all the scrolling components and their relationships
Widget | Build | Viewport |
---|---|---|
SingleChildScrollView | Scrollable | _SingleChildViewport |
ScrollView | Scrollable | ShrinkWrappingViewport/Viewport |
The Sliver family inherits from ScrollView
Widget | Extends |
---|---|
CustomScrollView | ScrollView |
NestedScrollView | CustomScrollView |
ListView/GridView | BoxScrollView => ScrollView |
The Scrollable component takes user gesture feedback, passes the Scrollable and Slivers to the Viewport and calculates the location of the Sliver. Note in silver can be a single child (SliverPadding SliverPersistentHeader/SliverToBoxAdapter, etc.) can also be many children (SliverList/SliverGrid). Below we through the analysis of the source code, explore the mystery.
ScrollView
Here is the key code in the build method. Here is the Scrollable we mentioned above, which is responsible for listening for user gestures.
final Scrollable scrollable = Scrollable(
dragStartBehavior: dragStartBehavior,
axisDirection: axisDirection,
controller: scrollController,
physics: physics,
semanticChildCount: semanticChildCount,
viewportBuilder: (BuildContext context, ViewportOffset offset) {
returnbuildViewport(context, offset, axisDirection, slivers); });Copy the code
Let’s look at the buildViewport method
@protected
Widget buildViewport(
BuildContext context,
ViewportOffset offset,
AxisDirection axisDirection,
List<Widget> slivers,
) {
if (shrinkWrap) {
return ShrinkWrappingViewport(
axisDirection: axisDirection,
offset: offset,
slivers: slivers,
);
}
return Viewport(
axisDirection: axisDirection,
offset: offset,
slivers: slivers,
cacheExtent: cacheExtent,
center: center,
anchor: anchor,
);
}
Copy the code
There are two types of viewports for shrinkWrap
Scrollable
For listening to various user gestures and scrolling, here is the key code in the build method.
// Use InheritedWidget to share position data
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.// Listen for gestures with the Listener, and call back scroll position with the viewportBuilder.
child: widget.viewportBuilder(context, position),
),
),
),
),
);
// You can see why Android and ios have different operations on Overscrolls
return _configuration.buildViewportChrome(context, result, widget.axisDirection);
Copy the code
Android and fuscia GlowingOverscrollIndicator used above to display the scroll after water ripple effect.
/// Wraps the given widget, which scrolls in the given [AxisDirection].
///
/// For example, on Android, this method wraps the given widget with a
/// [GlowingOverscrollIndicator] to provide visual feedback when the user
/// overscrolls.
Widget buildViewportChrome(BuildContext context, Widget child, AxisDirection axisDirection) {
// When modifying this function, consider modifying the implementation in
// _MaterialScrollBehavior as well.
switch (getPlatform(context)) {
case TargetPlatform.iOS:
return child;
case TargetPlatform.android:
case TargetPlatform.fuchsia:
return GlowingOverscrollIndicator(
child: child,
axisDirection: axisDirection,
color: _kDefaultGlowColor,
);
}
return null;
}
Copy the code
Viewport
Scroll visualizations are designed to reduce memory consumption by displaying (calculating) only a portion of the scroll view. For example, the ListView’s viewable area is 666 pixels, but the total height of its list elements is much higher than 666 pixels, but in fact we only care about the elements in this 666 pixels (plus this distance if we set CacheExtent).
Scrollable scrolling feedback and Slivers are passed to the Viewport in the Scrollview. Viewport is a MultiChildRenderObjectWidget, lei lei, this is a custom painting more components of the children. Go straight to the createRenderObject method and see that it returns a RenderViewport
RenderViewport
Let’s see what the construction parameters are.
RenderViewport({
// The main axis direction is down by default
AxisDirection axisDirection = AxisDirection.down,
// The vertical axis is related to the main axis
@required AxisDirection crossAxisDirection,
// User feedback for the Scrollable callback
@required ViewportOffset offset,
// When scrollOffset = 0, the first child is in the viewport position (0 <= Anchor <= 1.0), 0.0 in leading, 1.0 in trailing, and 0.5 in the middle
double anchor = 0.0./ / silver children
List<RenderSliver> children,
//The first child in the [GrowthDirection.forward] growth direction.
// This parameter is rarely used
RenderSliver center,
// Cache area size
double cacheExtent,
// Determine whether the cacheExtent is the actual size or the percentage of viewports
CacheExtentStyle cacheExtentStyle = CacheExtentStyle.pixel,
})... {
addAll(children);
if (center == null&& firstChild ! =null)
_center = firstChild;
}
Copy the code
You can see that all children are added in the construct, and if the center is not passed externally, the center defaults to the first child.
Highlight code analysis
sizedByParent
In Viewport this value always returns true,
@override
bool get sizedByParent => true;
Copy the code
Let’s take a look at the explanation of this property. If this value is true, then the component’s size is related only to the resize-constraints that its parent tells it, not to its children.
RenderViewport size constraints are told to it by its parent, regardless of the Slivers in it. Speaking of which let’s look at a code that beginners often make mistakes with.
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'test',
),
ListView.builder(itemBuilder: (context,index){})
],
),
Copy the code
We know that the ListView is ultimately a ScrollView, and that the Viewport in the Column has no way of knowing its valid size. This will cause the Viewport height to be infinite, which will cause an error (of course, you can set shrinkWrap to true here). But this will cause all the elements of the ListView to be counted, and the list will no longer scroll, which we’ll talk about later.)
If you go ahead and look at the code, you can see that when sizedByParent is true, you call the performResize method, specifying Size based only on constraints.
if (sizedByParent) {
assert(() {
_debugDoingThisResize = true;
return true; } ());try {
performResize();
assert(() {
debugAssertDoesMeetConstraints();
return true; } ()); }catch (e, stack) {
_debugReportException('performResize', e, stack);
}
assert(() {
_debugDoingThisResize = false;
return true; } ()); }Copy the code
performResize
Let’s see what’s going on in performResize for RenderViewport. There’s a whole bunch of asserts, just one sentence, I can’t be infinite. Finally, set your size to constraint.biggest. (Size is its own size, and constraints is the constraint given by parent)
@override
void performResize() {
assert(() {
if(! constraints.hasBoundedHeight || ! constraints.hasBoundedWidth) {switch (axis) {
case Axis.vertical:
if(! constraints.hasBoundedHeight) {throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('Vertical viewport was given unbounded height.'),
ErrorDescription(
'Viewports expand in the scrolling direction to fill their container. '
'In this case, a vertical viewport was given an unlimited amount of '
'vertical space in which to expand. This situation typically happens '
'when a scrollable widget is nested inside another scrollable widget.'
),
ErrorHint(
'If this widget is always nested in a scrollable widget there '
'is no need to use a viewport because there will always be enough '
'vertical space for the children. In this case, consider using a '
'Column instead. Otherwise, consider using the "shrinkWrap" property '
'(or a ShrinkWrappingViewport) to size the height of the viewport '
'to the sum of the heights of its children.')]); }if(! constraints.hasBoundedWidth) {throw FlutterError(
'Vertical viewport was given unbounded width.\n'
'Viewports expand in the cross axis to fill their container and '
'constrain their children to match their extent in the cross axis. '
'In this case, a vertical viewport was given an unlimited amount of '
'horizontal space in which to expand.'
);
}
break;
case Axis.horizontal:
if(! constraints.hasBoundedWidth) {throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('Horizontal viewport was given unbounded width.'),
ErrorDescription(
'Viewports expand in the scrolling direction to fill their container.'
'In this case, a horizontal viewport was given an unlimited amount of '
'horizontal space in which to expand. This situation typically happens '
'when a scrollable widget is nested inside another scrollable widget.'
),
ErrorHint(
'If this widget is always nested in a scrollable widget there '
'is no need to use a viewport because there will always be enough '
'horizontal space for the children. In this case, consider using a '
'Row instead. Otherwise, consider using the "shrinkWrap" property '
'(or a ShrinkWrappingViewport) to size the width of the viewport '
'to the sum of the widths of its children.')]); }if(! constraints.hasBoundedHeight) {throw FlutterError(
'Horizontal viewport was given unbounded height.\n'
'Viewports expand in the cross axis to fill their container and '
'constrain their children to match their extent in the cross axis. '
'In this case, a horizontal viewport was given an unlimited amount of '
'vertical space in which to expand.'
);
}
break; }}return true; } ()); size = constraints.biggest;// We ignore the return value of applyViewportDimension below because we are
// going to go through performLayout next regardless.
switch (axis) {
case Axis.vertical:
offset.applyViewportDimension(size.height);
break;
case Axis.horizontal:
offset.applyViewportDimension(size.width);
break; }}Copy the code
performLayout
Children responsible for laying out RenderViewport
// Get the size of the main axis and the vertical axis from size
double mainAxisExtent;
double crossAxisExtent;
switch (axis) {
case Axis.vertical:
mainAxisExtent = size.height;
crossAxisExtent = size.width;
break;
case Axis.horizontal:
mainAxisExtent = size.width;
crossAxisExtent = size.height;
break;
}
// If the single Sliver child's Viewport height is 100, Anchor is 0.5, and centerOffsetAdjustment is 50.0, then the center will be right in the middle of the viewport when the Scroll offset is 0.0.
final double centerOffsetAdjustment = center.centerOffsetAdjustment;
double correction;
int count = 0;
do {
assert(offset.pixels ! =null);
correction = _attemptLayout(mainAxisExtent, crossAxisExtent, offset.pixels + centerOffsetAdjustment);
///If it is not 0.0, it is because there is a correction in the Child (which we will cover later in this series, but we will simply assume that there is a problem with the Layout Child here), and we need to change the Scroll offset and then rearrange the layout chilren.
if(correction ! =0.0) {
offset.correctBy(correction);
} else {
///Tell the Scrollable the minimum and maximum scrolling distances
if (offset.applyContentDimensions(
math.min(0.0, _minScrollExtent + mainAxisExtent * anchor),
math.max(0.0, _maxScrollExtent - mainAxisExtent * (1.0 - anchor)),
))
break;
}
count += 1;
} while (count < _maxLayoutCycles);
Copy the code
If the maximum number of times is exceeded and there is still a problem with children or layout, the warning will be displayed.
Now let’s look at what was done in the _attemptLayout method.
double _attemptLayout(double mainAxisExtent, double crossAxisExtent, double correctedOffset) {
assert(! mainAxisExtent.isNaN);assert(mainAxisExtent >= 0.0);
assert(crossAxisExtent.isFinite);
assert(crossAxisExtent >= 0.0);
assert(correctedOffset.isFinite);
_minScrollExtent = 0.0;
_maxScrollExtent = 0.0;
_hasVisualOverflow = false;
// The value of centerOffset will be corrected using Anchor and offset.pixels + centerOffsetAdjustment. In front of speak
final double centerOffset = mainAxisExtent * anchor - correctedOffset;
// RemainingPaintExtent (); // RemainingPaintExtent ()
final double reverseDirectionRemainingPaintExtent = centerOffset.clamp(0.0, mainAxisExtent);
// RemainingPaintExtent; // RemainingPaintExtent; // RemainingPaintExtent
final double forwardDirectionRemainingPaintExtent = (mainAxisExtent - centerOffset).clamp(0.0, mainAxisExtent);
switch (cacheExtentStyle) {
case CacheExtentStyle.pixel:
_calculatedCacheExtent = cacheExtent;
break;
case CacheExtentStyle.viewport:
_calculatedCacheExtent = mainAxisExtent * cacheExtent;
break;
}
///The total calculation area contains two Cacheextents before and after
final double fullCacheExtent = mainAxisExtent + 2 * _calculatedCacheExtent;
///Add the center location of the cacheExtent to the size of the cache extent
final double centerCacheOffset = centerOffset + _calculatedCacheExtent;
// RemainingPaintExtent; // RemainingPaintExtent; // RemainingPaintExtent
final double reverseDirectionRemainingCacheExtent = centerCacheOffset.clamp(0.0, fullCacheExtent);
// RemainingPaintExtent; // RemainingPaintExtent; // RemainingPaintExtent; // RemainingPaintExtent
final double forwardDirectionRemainingCacheExtent = (fullCacheExtent - centerCacheOffset).clamp(0.0, fullCacheExtent);
final RenderSliver leadingNegativeChild = childBefore(center);
///If there is a child before the center, the layout child will be forward, calculating the child before the previous layout
if(leadingNegativeChild ! =null) {
// negative scroll offsets
final double result = layoutChildSequence(
child: leadingNegativeChild,
scrollOffset: math.max(mainAxisExtent, centerOffset) - mainAxisExtent,
overlap: 0.0,
layoutOffset: forwardDirectionRemainingPaintExtent,
remainingPaintExtent: reverseDirectionRemainingPaintExtent,
mainAxisExtent: mainAxisExtent,
crossAxisExtent: crossAxisExtent,
growthDirection: GrowthDirection.reverse,
advance: childBefore,
remainingCacheExtent: reverseDirectionRemainingCacheExtent,
cacheOrigin: (mainAxisExtent - centerOffset).clamp(-_calculatedCacheExtent, 0.0));if(result ! =0.0)
return -result;
}
///Layout child after center
// positive scroll offsets
return layoutChildSequence(
child: center,
scrollOffset: math.max(0.0, -centerOffset),
overlap: leadingNegativeChild == null ? math.min(0.0, -centerOffset) : 0.0,
layoutOffset: centerOffset >= mainAxisExtent ? centerOffset: reverseDirectionRemainingPaintExtent,
remainingPaintExtent: forwardDirectionRemainingPaintExtent,
mainAxisExtent: mainAxisExtent,
crossAxisExtent: crossAxisExtent,
growthDirection: GrowthDirection.forward,
advance: childAfter,
remainingCacheExtent: forwardDirectionRemainingCacheExtent,
cacheOrigin: centerOffset.clamp(-_calculatedCacheExtent, 0.0)); }Copy the code
Notice that scrollOffset is different when it comes to the forward layout and the backward layout, One is Math.max (mainAxisExtent, centerOffset) -MainAxisextent and the other is Math.max (0.0, centerOffset). If there are multiple slivers in the viewport, we can specify one of them as center(by default, the first one is center). If you want to roll forward, the centerOffset will be larger, and if you want to roll back, the centerOffset will be negative. This still feels a little abstract, but for a chestnut, I added a key to the second silver and assigned the center of the CustomScrollView to this key. SHH, Center is a parameter THAT I estimate 99 percent of people have not used. If you have, please leave a message and I’ll see how many people know about it.
CustomScrollView(
center: key,
slivers: <Widget>[
SliverList(),
SliverGrid(key:key),
Copy the code
The SliverGrid is at its initial position when the initial centerOffset is 0.Scrolling forward, you can see that we get the reverse SliverList, which can also be verified from our arguments. Offset. Pixels (the scrollposition of the ScollView) should also be equal to 0.
If you look at the layoutChildSequence method, notice that the advance method actually calls childBefore, and the backward method calls childAfter
double layoutChildSequence({
@required RenderSliver child,
@required double scrollOffset,
@required double overlap,
@required double layoutOffset,
@required double remainingPaintExtent,
@required double mainAxisExtent,
@required double crossAxisExtent,
@required GrowthDirection growthDirection,
@required RenderSliver advance(RenderSliver child),
@required double remainingCacheExtent,
@required double cacheOrigin,
}) {
assert(scrollOffset.isFinite);
assert(scrollOffset >= 0.0);
final double initialLayoutOffset = layoutOffset;
final ScrollDirection adjustedUserScrollDirection =
applyGrowthDirectionToScrollDirection(offset.userScrollDirection, growthDirection);
assert(adjustedUserScrollDirection ! =null);
double maxPaintOffset = layoutOffset + overlap;
double precedingScrollExtent = 0.0;
while(child ! =null) {
final double sliverScrollOffset = scrollOffset <= 0.0 ? 0.0 : scrollOffset;
// If the scrollOffset is too small we adjust the paddedOrigin because it
// doesn't make sense to ask a sliver for content before its scroll
// offset.
final double correctedCacheOrigin = math.max(cacheOrigin, -sliverScrollOffset);
final double cacheExtentCorrection = cacheOrigin - correctedCacheOrigin;
assert(sliverScrollOffset >= correctedCacheOrigin.abs());
assert(correctedCacheOrigin <= 0.0);
assert(sliverScrollOffset >= 0.0);
assert(cacheExtentCorrection <= 0.0);
/ / input
child.layout(SliverConstraints(
axisDirection: axisDirection,
growthDirection: growthDirection,
userScrollDirection: adjustedUserScrollDirection,
scrollOffset: sliverScrollOffset,
precedingScrollExtent: precedingScrollExtent,
overlap: maxPaintOffset - layoutOffset,
remainingPaintExtent: math.max(0.0, remainingPaintExtent - layoutOffset + initialLayoutOffset),
crossAxisExtent: crossAxisExtent,
crossAxisDirection: crossAxisDirection,
viewportMainAxisExtent: mainAxisExtent,
remainingCacheExtent: math.max(0.0, remainingCacheExtent + cacheExtentCorrection),
cacheOrigin: correctedCacheOrigin,
), parentUsesSize: true);
/ / output
final SliverGeometry childLayoutGeometry = child.geometry;
assert(childLayoutGeometry.debugAssertIsValid());
// If there is a correction to apply, we'll have to start over.
if(childLayoutGeometry.scrollOffsetCorrection ! =null)
return childLayoutGeometry.scrollOffsetCorrection;
// We use the child's paint origin in our coordinate system as the
// layoutOffset we store in the child's parent data.
final double effectiveLayoutOffset = layoutOffset + childLayoutGeometry.paintOrigin;
// `effectiveLayoutOffset` becomes meaningless once we moved past the trailing edge
// because `childLayoutGeometry.layoutExtent` is zero. Using the still increasing
// 'scrollOffset` to roughly position these invisible slivers in the right order.
if (childLayoutGeometry.visible || scrollOffset > 0) {
updateChildLayoutOffset(child, effectiveLayoutOffset, growthDirection);
} else {
updateChildLayoutOffset(child, -scrollOffset + initialLayoutOffset, growthDirection);
}
// Update the maximum draw position
maxPaintOffset = math.max(effectiveLayoutOffset + childLayoutGeometry.paintExtent, maxPaintOffset);
scrollOffset -= childLayoutGeometry.scrollExtent;
// The scrolling distance of the previous child
precedingScrollExtent += childLayoutGeometry.scrollExtent;
layoutOffset += childLayoutGeometry.layoutExtent;
if(childLayoutGeometry.cacheExtent ! =0.0) {
remainingCacheExtent -= childLayoutGeometry.cacheExtent - cacheExtentCorrection;
cacheOrigin = math.min(correctedCacheOrigin + childLayoutGeometry.cacheExtent, 0.0);
}
// Update _maxScrollExtent and _minScrollExtent
// https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/rendering/viewport.dart#L1449
updateOutOfBandData(growthDirection, childLayoutGeometry);
// move on to the next child
// Layout the next child
child = advance(child);
}
// we made it without a correction, whee!
// Perfect, all children are not wrong
return 0.0;
}
Copy the code
SliverConstraints is the input for the layout child, and SliverGeometry is the output after the layout child. After the layout, the viewport will update _maxScrollExtent and _minScrollExtent. Then layout the next sliver. We’ll cover the contents of the child-layout method in the next chapter.
RenderShrinkWrappingViewport
When we put the shrinkWrap is set to true, the Viewport using the RenderShrinkWrappingViewport. So let’s see what the difference is. Let’s start with the official explanation of the shrinkWrap parameter. Set shrinkWrap to true, and the viewport size will be determined not by its parent, but by the viewport itself. The size of the Viewport in the ListView is what the parent tells it to be. The size of the Viewport in the ListView is what the parent tells it to be.
Parent: Hi child, how old are you? I give you a limit on the size of the infinite vertical axis.
Child: Hi, parent, I don’t know, you didn’t tell me how big my viewport is. Then I can only know my total size by layout out all my children. That I have to change a viewport, know RenderShrinkWrappingViewport to calculate my total height.
Due to the ListView parent cannot tell its child ListView can measure the size, so we must set the shrinkWrap to true, internal use RenderShrinkWrappingViewport calculation.
Due to the size of the RenderShrinkWrappingViewport no longer only decided by the parent, so no longer call performResize method. So let’s focus on the performLayout method.
performLayout
@override
void performLayout() {
if (firstChild == null) {
switch (axis) {
case Axis.vertical:
// If it is vertical, can you at least tell me the maximum horizontal limit?
assert(constraints.hasBoundedWidth);
size = Size(constraints.maxWidth, constraints.minHeight);
break;
// If it is horizontal, can you at least tell me the maximum vertical limit?
case Axis.horizontal:
assert(constraints.hasBoundedHeight);
size = Size(constraints.minWidth, constraints.maxHeight);
break;
}
offset.applyViewportDimension(0.0);
_maxScrollExtent = 0.0;
_shrinkWrapExtent = 0.0;
_hasVisualOverflow = false;
offset.applyContentDimensions(0.0.0.0);
return;
}
double mainAxisExtent;
double crossAxisExtent;
switch (axis) {
case Axis.vertical:
// If it is vertical, can you at least tell me the maximum horizontal limit? Speaking of which, I remember why the Flutter does not have a container that supports horizontal and vertical rolling.
assert(constraints.hasBoundedWidth);
mainAxisExtent = constraints.maxHeight;
crossAxisExtent = constraints.maxWidth;
break;
case Axis.horizontal:
assert(constraints.hasBoundedHeight);
// If it is horizontal, can you at least tell me the maximum vertical limit?
mainAxisExtent = constraints.maxWidth;
crossAxisExtent = constraints.maxHeight;
break;
}
double correction;
double effectiveExtent;
do {
assert(offset.pixels ! =null);
correction = _attemptLayout(mainAxisExtent, crossAxisExtent, offset.pixels);
if(correction ! =0.0) {
offset.correctBy(correction);
} else {
switch (axis) {
case Axis.vertical:
effectiveExtent = constraints.constrainHeight(_shrinkWrapExtent);
break;
case Axis.horizontal:
effectiveExtent = constraints.constrainWidth(_shrinkWrapExtent);
break;
}
final bool didAcceptViewportDimension = offset.applyViewportDimension(effectiveExtent);
final bool didAcceptContentDimension = offset.applyContentDimensions(0.0, math.max(0.0, _maxScrollExtent - effectiveExtent));
if (didAcceptViewportDimension && didAcceptContentDimension)
break; }}while (true);
switch (axis) {
case Axis.vertical:
size = constraints.constrainDimensions(crossAxisExtent, effectiveExtent);
break;
case Axis.horizontal:
size = constraints.constrainDimensions(effectiveExtent, crossAxisExtent);
break; }}Copy the code
Both _maxScrollExtent and _shrinkWrapExtent are key players. When mainAxisExtent is not double.Infinity, the effect is the same as calculated in Viewport (excluding Center). When the mainAxisExtent is double.Infinity, we will layout out all the children to get the total size
The key code
@override
void updateOutOfBandData(GrowthDirection growthDirection, SliverGeometry childLayoutGeometry) {
assert(growthDirection == GrowthDirection.forward);
_maxScrollExtent += childLayoutGeometry.scrollExtent;
if (childLayoutGeometry.hasVisualOverflow)
_hasVisualOverflow = true;
_shrinkWrapExtent += childLayoutGeometry.maxPaintExtent;
}
Copy the code
This is why we said that if we put a ListView inside a Column or a ListView, the ListView will build all the elements and lose scrolling.
According to the plot
This chapter may seem a little boring, but it’s all about source code analysis. In the next chapter (ExtendedList of enemies of Flutter Sliver), We will follow the ListView/GridView = > SliverList/SliverGrid = > RenderSliverList/RenderSliverGrid feelings line, understand how will children eventually in silver is mapped. In the next chapter, we’ll do more than just dry source analysis. We’ll show you how to handle the image list memory explosion, show you how to layout the list elements, and so on.
conclusion
Both the ExtendedList WaterfallFlow and LoadingMoreList are edible states. Can’t wait for friends to eat in advance, especially the picture list memory is too large and lead to flash out of friends can first see the demo, first to solve the problem that has been tormenting everyone
Welcome to joinFlutter CandiesTogether they produce cute little candies called FlutterQQ group: 181398081
Finally put the Flutter on the bucket. It smells good.