@@ -111,8 +111,15 @@ class SliverMultiBoxAdaptorParentData extends SliverLogicalParentData with Conta
111111 /// The index of this child according to the [RenderSliverBoxChildManager] .
112112 int index;
113113
114+ /// Whether to keep the child alive even when it is no longer visible.
115+ bool keepAlive = false ;
116+
117+ /// Whether the widget is currently in the
118+ /// [RenderSliverMultiBoxAdaptor._keepAliveBucket] .
119+ bool _keptAlive = false ;
120+
114121 @override
115- String toString () => 'index=$index ; ${super .toString ()}' ;
122+ String toString () => 'index=$index ; ${keepAlive == true ? "keepAlive; " : "" }${ super .toString ()}' ;
116123}
117124
118125/// A sliver with multiple box children.
@@ -168,10 +175,15 @@ abstract class RenderSliverMultiBoxAdaptor extends RenderSliver
168175 RenderSliverBoxChildManager get childManager => _childManager;
169176 final RenderSliverBoxChildManager _childManager;
170177
178+ /// The nodes being kept alive despite not being visible.
179+ final Map <int , RenderBox > _keepAliveBucket = < int , RenderBox > {};
180+
171181 @override
172182 void adoptChild (RenderObject child) {
173183 super .adoptChild (child);
174- childManager.didAdoptChild (child);
184+ final SliverMultiBoxAdaptorParentData childParentData = child.parentData;
185+ if (! childParentData._keptAlive)
186+ childManager.didAdoptChild (child);
175187 }
176188
177189 bool _debugAssertChildListLocked () => childManager.debugAssertChildListLocked ();
@@ -192,64 +204,139 @@ abstract class RenderSliverMultiBoxAdaptor extends RenderSliver
192204 });
193205 }
194206
207+ @override
208+ void remove (RenderBox child) {
209+ final SliverMultiBoxAdaptorParentData childParentData = child.parentData;
210+ if (! childParentData._keptAlive) {
211+ super .remove (child);
212+ return ;
213+ }
214+ assert (_keepAliveBucket[childParentData.index] == child);
215+ _keepAliveBucket.remove (childParentData.index);
216+ dropChild (child);
217+ }
218+
219+ @override
220+ void removeAll () {
221+ super .removeAll ();
222+ for (RenderBox child in _keepAliveBucket.values)
223+ dropChild (child);
224+ _keepAliveBucket.clear ();
225+ }
226+
227+ void _createOrObtainChild (int index, { RenderBox after }) {
228+ invokeLayoutCallback <SliverConstraints >((SliverConstraints constraints) {
229+ assert (constraints == this .constraints);
230+ if (_keepAliveBucket.containsKey (index)) {
231+ final RenderBox child = _keepAliveBucket.remove (index);
232+ final SliverMultiBoxAdaptorParentData childParentData = child.parentData;
233+ assert (childParentData._keptAlive);
234+ dropChild (child);
235+ child.parentData = childParentData;
236+ insert (child, after: after);
237+ childParentData._keptAlive = false ;
238+ } else {
239+ _childManager.createChild (index, after: after);
240+ }
241+ });
242+ }
243+
244+ void _destroyOrCacheChild (RenderBox child) {
245+ final SliverMultiBoxAdaptorParentData childParentData = child.parentData;
246+ if (childParentData.keepAlive) {
247+ assert (! childParentData._keptAlive);
248+ remove (child);
249+ _keepAliveBucket[childParentData.index] = child;
250+ child.parentData = childParentData;
251+ super .adoptChild (child);
252+ childParentData._keptAlive = true ;
253+ } else {
254+ assert (child.parent == this );
255+ _childManager.removeChild (child);
256+ assert (child.parent == null );
257+ }
258+ }
259+
260+ @override
261+ void attach (PipelineOwner owner) {
262+ super .attach (owner);
263+ for (RenderBox child in _keepAliveBucket.values)
264+ child.attach (owner);
265+ }
266+
267+ @override
268+ void detach () {
269+ super .detach ();
270+ for (RenderBox child in _keepAliveBucket.values)
271+ child.detach ();
272+ }
273+
274+ @override
275+ void redepthChildren () {
276+ super .redepthChildren ();
277+ for (RenderBox child in _keepAliveBucket.values)
278+ redepthChild (child);
279+ }
280+
281+ @override
282+ void visitChildren (RenderObjectVisitor visitor) {
283+ super .visitChildren (visitor);
284+ for (RenderBox child in _keepAliveBucket.values)
285+ visitor (child);
286+ }
287+
195288 /// Called during layout to create and add the child with the given index and
196289 /// scroll offset.
197290 ///
198291 /// Calls [RenderSliverBoxChildManager.createChild] to actually create and add
199- /// the child.
292+ /// the child if necessary. The child may instead be obtained from a cache;
293+ /// see [SliverMultiBoxAdaptorParentData.keepAlive] .
200294 ///
201- /// Returns false if createChild did not add any child, otherwise returns
202- /// true.
295+ /// Returns false if there was no cached child and `createChild` did not add
296+ /// any child, otherwise returns true.
203297 ///
204298 /// Does not layout the new child.
205299 ///
206- /// When this is called, there are no children, so no children can be removed
207- /// during the call to createChild. No child should be added during that call
208- /// either, except for the one that is created and returned by createChild.
300+ /// When this is called, there are no visible children, so no children can be
301+ /// removed during the call to `createChild` . No child should be added during
302+ /// that call either, except for the one that is created and returned by
303+ /// `createChild` .
209304 @protected
210305 bool addInitialChild ({ int index: 0 , double layoutOffset: 0.0 }) {
211306 assert (_debugAssertChildListLocked ());
212307 assert (firstChild == null );
213- bool result;
214- invokeLayoutCallback <SliverConstraints >((SliverConstraints constraints) {
215- assert (constraints == this .constraints);
216- _childManager.createChild (index, after: null );
217- if (firstChild != null ) {
218- assert (firstChild == lastChild);
219- assert (indexOf (firstChild) == index);
220- final SliverMultiBoxAdaptorParentData firstChildParentData = firstChild.parentData;
221- firstChildParentData.layoutOffset = layoutOffset;
222- result = true ;
223- } else {
224- childManager.setDidUnderflow (true );
225- result = false ;
226- }
227- });
228- return result;
308+ _createOrObtainChild (index, after: null );
309+ if (firstChild != null ) {
310+ assert (firstChild == lastChild);
311+ assert (indexOf (firstChild) == index);
312+ final SliverMultiBoxAdaptorParentData firstChildParentData = firstChild.parentData;
313+ firstChildParentData.layoutOffset = layoutOffset;
314+ return true ;
315+ }
316+ childManager.setDidUnderflow (true );
317+ return false ;
229318 }
230319
231320 /// Called during layout to create, add, and layout the child before
232321 /// [firstChild] .
233322 ///
234323 /// Calls [RenderSliverBoxChildManager.createChild] to actually create and add
235- /// the child.
324+ /// the child if necessary. The child may instead be obtained from a cache;
325+ /// see [SliverMultiBoxAdaptorParentData.keepAlive] .
236326 ///
237- /// Returns the new child or null if no child is created .
327+ /// Returns the new child or null if no child was obtained .
238328 ///
239329 /// The child that was previously the first child, as well as any subsequent
240330 /// children, may be removed by this call if they have not yet been laid out
241331 /// during this layout pass. No child should be added during that call except
242- /// for the one that is created and returned by createChild.
332+ /// for the one that is created and returned by ` createChild` .
243333 @protected
244334 RenderBox insertAndLayoutLeadingChild (BoxConstraints childConstraints, {
245335 bool parentUsesSize: false ,
246336 }) {
247337 assert (_debugAssertChildListLocked ());
248338 final int index = indexOf (firstChild) - 1 ;
249- invokeLayoutCallback <SliverConstraints >((SliverConstraints constraints) {
250- assert (constraints == this .constraints);
251- _childManager.createChild (index, after: null );
252- });
339+ _createOrObtainChild (index, after: null );
253340 if (indexOf (firstChild) == index) {
254341 firstChild.layout (childConstraints, parentUsesSize: parentUsesSize);
255342 return firstChild;
@@ -262,7 +349,8 @@ abstract class RenderSliverMultiBoxAdaptor extends RenderSliver
262349 /// the given child.
263350 ///
264351 /// Calls [RenderSliverBoxChildManager.createChild] to actually create and add
265- /// the child.
352+ /// the child if necessary. The child may instead be obtained from a cache;
353+ /// see [SliverMultiBoxAdaptorParentData.keepAlive] .
266354 ///
267355 /// Returns the new child. It is the responsibility of the caller to configure
268356 /// the child's scroll offset.
@@ -277,13 +365,9 @@ abstract class RenderSliverMultiBoxAdaptor extends RenderSliver
277365 assert (_debugAssertChildListLocked ());
278366 assert (after != null );
279367 final int index = indexOf (after) + 1 ;
280- invokeLayoutCallback <SliverConstraints >((SliverConstraints constraints) {
281- assert (constraints == this .constraints);
282- _childManager.createChild (index, after: after);
283- });
368+ _createOrObtainChild (index, after: after);
284369 final RenderBox child = childAfter (after);
285370 if (child != null && indexOf (child) == index) {
286- assert (indexOf (child) == index);
287371 child.layout (childConstraints, parentUsesSize: parentUsesSize);
288372 return child;
289373 }
@@ -293,19 +377,37 @@ abstract class RenderSliverMultiBoxAdaptor extends RenderSliver
293377
294378 /// Called after layout with the number of children that can be garbage
295379 /// collected at the head and tail of the child list.
380+ ///
381+ /// Children whose [SliverMultiBoxAdaptorParentData.keepAlive] property is
382+ /// set to true will be removed to a cache instead of being dropped.
383+ ///
384+ /// This method also collects any children that were previously kept alive but
385+ /// are now no longer necessary. As such, it should be called every time
386+ /// [performLayout] is run, even if the arguments are both zero.
296387 @protected
297388 void collectGarbage (int leadingGarbage, int trailingGarbage) {
298389 assert (_debugAssertChildListLocked ());
299390 assert (childCount >= leadingGarbage + trailingGarbage);
300391 invokeLayoutCallback <SliverConstraints >((SliverConstraints constraints) {
301392 while (leadingGarbage > 0 ) {
302- _childManager. removeChild (firstChild);
393+ _destroyOrCacheChild (firstChild);
303394 leadingGarbage -= 1 ;
304395 }
305396 while (trailingGarbage > 0 ) {
306- _childManager. removeChild (lastChild);
397+ _destroyOrCacheChild (lastChild);
307398 trailingGarbage -= 1 ;
308399 }
400+ // Ask the child manager to remove the children that are no longer being
401+ // kept alive. (This should cause _keepAliveBucket to change, so we have
402+ // to prepare our list ahead of time.)
403+ _keepAliveBucket.values.where ((RenderBox child) {
404+ final SliverMultiBoxAdaptorParentData childParentData = child.parentData;
405+ return ! childParentData.keepAlive;
406+ }).toList ().forEach (_childManager.removeChild);
407+ assert (_keepAliveBucket.values.where ((RenderBox child) {
408+ final SliverMultiBoxAdaptorParentData childParentData = child.parentData;
409+ return ! childParentData.keepAlive;
410+ }).isEmpty);
309411 });
310412 }
311413
@@ -442,4 +544,42 @@ abstract class RenderSliverMultiBoxAdaptor extends RenderSliver
442544 });
443545 return true ;
444546 }
547+
548+ @override
549+ String debugDescribeChildren (String prefix) {
550+ StringBuffer result;
551+ if (firstChild != null ) {
552+ result = new StringBuffer ()
553+ ..write (prefix)
554+ ..write (' \u 2502\n ' );
555+ RenderBox child = firstChild;
556+ while (child != lastChild) {
557+ final SliverMultiBoxAdaptorParentData childParentData = child.parentData;
558+ result.write (child.toStringDeep ("$prefix \u 251C\u 2500child with index ${childParentData .index }: " , "$prefix \u 2502" ));
559+ child = childParentData.nextSibling;
560+ }
561+ if (child != null ) {
562+ assert (child == lastChild);
563+ final SliverMultiBoxAdaptorParentData childParentData = child.parentData;
564+ if (_keepAliveBucket.isEmpty) {
565+ result.write (child.toStringDeep ("$prefix \u 2514\u 2500child with index ${childParentData .index }: " , "$prefix " ));
566+ } else {
567+ result.write (child.toStringDeep ("$prefix \u 251C\u 2500child with index ${childParentData .index }: " , "$prefix \u 254E" ));
568+ }
569+ }
570+ }
571+ if (_keepAliveBucket.isNotEmpty) {
572+ result ?? = new StringBuffer ()
573+ ..write (prefix)
574+ ..write (' \u 254E\n ' );
575+ final List <int > indices = _keepAliveBucket.keys.toList ()..sort ();
576+ final int lastIndex = indices.removeLast ();
577+ if (indices.isNotEmpty) {
578+ for (int index in indices)
579+ result.write (_keepAliveBucket[index].toStringDeep ("$prefix \u 251C\u 2500child with index $index (kept alive offstage): " , "$prefix \u 254E" ));
580+ }
581+ result.write (_keepAliveBucket[lastIndex].toStringDeep ("$prefix \u 2514\u 2500child with index $lastIndex (kept alive offstage): " , "$prefix " ));
582+ }
583+ return result? .toString () ?? '' ;
584+ }
445585}
0 commit comments