How does the Flutter ListView scroll in this article? So today let’s take a look at how items can be reused.
The following two GIFs are written in code, suddenly feel that drawing software is not so easy to use. Will knock code, I want to draw what to draw what, how to draw how to draw!
1. SliverConstraints instructions
Get to the point. ScrollOffset is the slide offset.
2. Look at the performLayout method
The performLayout method is close to 300, although there are a lot of comments, but it also looks like a headache! Line by line, you can try 😩😩😩.
2.1 ListView initialization layout procedure
Let’s do the picture first, because I think it’s easier to do the picture first than the code first.
That’s easy, so let’s go through the process in code.
First, we’re going to insert, and then we’re going to lay out the first child.
class RenderSliverList extends RenderSliverMultiBoxAdaptor { ... // Make sure we have at least one child to start from. if (firstChild == null) { if (! addInitialChild()) { ... // insert firstChild}}... assert(earliestUsefulChild == firstChild); // Make sure we've laid out at least one child. if (leadingChildWithLayout == null) { earliestUsefulChild! .layout(childConstraints, parentUsesSize: true); . }... }Copy the code
The next piece of code inserts the child. Code is so long, I don’t want to insert code…
[bug Mc-10864] – nugget inserts code block components that don’t recognize their own line feeds!!
bool inLayoutRange = true; RenderBox? child = earliestUsefulChild; int index = indexOf(child!) ; double endScrollOffset = childScrollOffset(child)! + paintExtentOf(child); bool advance() { // returns true if we advanced, false if we have no more children // This function is used in two different places below, to avoid code duplication. assert(child ! = null); if (child == trailingChildWithLayout) inLayoutRange = false; child = childAfter(child!) ; if (child == null) inLayoutRange = false; index += 1; if (! inLayoutRange) { if (child == null || indexOf(child!) ! = index) { // We are missing a child. Insert it (and lay it out) if possible. child = insertAndLayoutChild(childConstraints, after: trailingChildWithLayout, parentUsesSize: true, ); if (child == null) { // We have run out of children. return false; } } else { // Lay out the child. child! .layout(childConstraints, parentUsesSize: true); } trailingChildWithLayout = child; } assert(child ! = null); final SliverMultiBoxAdaptorParentData childParentData = child! .parentData as SliverMultiBoxAdaptorParentData; childParentData.layoutOffset = endScrollOffset; assert(childParentData.index == index); endScrollOffset = childScrollOffset(child!) ! + paintExtentOf(child!) ; return true; }Copy the code
Here’s the key: the following judgment determines whether to continue inserting the child.
while (endScrollOffset < targetEndScrollOffset) {
if (!advance()) {
reachedEnd = true;
break;
}
}
Copy the code
So here’s a question, what is a cacheExtent and why is it 160?
CacheExtent is the cache of the view, and 160 is just a random thing. 😏 😏 😏
Well, cacheExtent has a system default value.
abstract class RenderAbstractViewport extends RenderObject {
...
static const double defaultCacheExtent = 250.0;
}
Copy the code
250, are you happy 😛😛😛
Initialization finished, time to roll…
Wait, what are the conditions? Don’t panic, the bottom is more exciting.
2.2 Slide parsing on ListView
Let’s start with a picture. A picture is worth a thousand words. I don’t even want to talk about the code…
The properties are all in the diagram, and the relationship is described in code.
fakeScrollOffset; // Note that cacheOrigin is negative, with a minimum of -250 // scrollOffset is the offset for the view to slide off the screen, including the buffer, The first 250 pixels are in zero units scrollOffset = fakeScrollOffset + cacheOrigin; FullCacheExtent = mainAxisExtent + 2 * _caculatedCacheExtent; RemainCacheExtent = (mainAxisExtent + _caculatedCacheExtent + offset. Pixels).clamp(0, fullCacheExtent); // remainCacheExtent = (mainAxisExtent + _caculatedCacheExtent + offset. TargetEndScrollOffset = scrollOffset + remainCacheExtent; // Note that this is not linearly increasing! LastChild offset + child draw range endScrollOffset = childScrollOffset(lastChild) + paintExtentOf(lastChild);Copy the code
Who else! (Can speak more clearly than I can)
2.3 How does the bottom of the ListView change?
Don’t want to speak.
2.4 How does the head change when sliding on the ListView?
Don’t want to speak.
2.5 ListView time to slide
Don’t want to speak.
2.6 This section describes the performLayout method
When I “tap” on the top GIF, I feel like I implemented performLayout…
It’s just a simplified version. Let’s take a look at the rest of performLayout.
This code retrieves the top child.
if (childScrollOffset(firstChild!) == null) { int leadingChildrenWithoutLayoutOffset = 0; while (childScrollOffset(earliestUsefulChild!) == null) { earliestUsefulChild = childAfter(firstChild!) ; leadingChildrenWithoutLayoutOffset += 1; } // We should be able to destroy children with null layout offset safely, // because they are likely outside of viewport collectGarbage(leadingChildrenWithoutLayoutOffset, 0); assert(firstChild ! = null); }Copy the code
This piece of code mainly determines whether a child needs to be added to the header during the slide. And some other complications.
earliestUsefulChild = firstChild; for (double earliestScrollOffset = childScrollOffset(earliestUsefulChild!) ! ; earliestScrollOffset > scrollOffset; earliestScrollOffset = childScrollOffset(earliestUsefulChild)!) { // We have to add children before the earliestUsefulChild. earliestUsefulChild = insertAndLayoutLeadingChild(childConstraints, parentUsesSize: true); if (earliestUsefulChild == null) { final SliverMultiBoxAdaptorParentData childParentData = firstChild! .parentData as SliverMultiBoxAdaptorParentData; ChildParentData. LayoutOffset = 0.0; If (scrollOffset = = 0.0) {/ / insertAndLayoutLeadingChild only lays out the children before / / firstChild. In this case, nothing has been laid out. We have // to lay out firstChild manually. firstChild! .layout(childConstraints, parentUsesSize: true); earliestUsefulChild = firstChild; leadingChildWithLayout = earliestUsefulChild; trailingChildWithLayout ?? = earliestUsefulChild; break; } else { // We ran out of children before reaching the scroll offset. // We must inform our parent that this sliver cannot fulfill // its contract and that we need a scroll offset correction. geometry = SliverGeometry( scrollOffsetCorrection: -scrollOffset, ); return; } } final double firstChildScrollOffset = earliestScrollOffset - paintExtentOf(firstChild!) ; // firstChildScrollOffset may contain double precision error if (firstChildScrollOffset < -precisionErrorTolerance) { // Let's assume there is no child before the first child. We will // correct it on the next layout if it is not. geometry = SliverGeometry( scrollOffsetCorrection: -firstChildScrollOffset, ); final SliverMultiBoxAdaptorParentData childParentData = firstChild! .parentData as SliverMultiBoxAdaptorParentData; ChildParentData. LayoutOffset = 0.0; return; } final SliverMultiBoxAdaptorParentData childParentData = earliestUsefulChild.parentData as SliverMultiBoxAdaptorParentData; childParentData.layoutOffset = firstChildScrollOffset; assert(earliestUsefulChild == firstChild); leadingChildWithLayout = earliestUsefulChild; trailingChildWithLayout ?? = earliestUsefulChild; }Copy the code
Slide to the top of some judgment processing.
if (scrollOffset < precisionErrorTolerance) { // We iterate from the firstChild in case the leading child has a 0 paint // extent. while (indexOf(firstChild!) > 0) { final double earliestScrollOffset = childScrollOffset(firstChild!) ! ; // We correct one child at a time. If there are more children before // the earliestUsefulChild, we will correct it once the scroll offset // reaches zero again. earliestUsefulChild = insertAndLayoutLeadingChild(childConstraints, parentUsesSize: true); assert(earliestUsefulChild ! = null); final double firstChildScrollOffset = earliestScrollOffset - paintExtentOf(firstChild!) ; final SliverMultiBoxAdaptorParentData childParentData = firstChild! .parentData as SliverMultiBoxAdaptorParentData; ChildParentData. LayoutOffset = 0.0; // We only need to correct if the leading child actually has a // paint extent. if (firstChildScrollOffset < -precisionErrorTolerance) { geometry = SliverGeometry( scrollOffsetCorrection: -firstChildScrollOffset, ); return; }}}Copy the code
The rest of the code is basically the bottom child recycling, and calculate geometry.
3. The last
Don’t get bogged down in code details. Know its meaning, forget its form, no move win a move.