@@ -111,8 +111,15 @@ class SliverMultiBoxAdaptorParentData extends SliverLogicalParentData with Conta
111
111
/// The index of this child according to the [RenderSliverBoxChildManager] .
112
112
int index;
113
113
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
+
114
121
@override
115
- String toString () => 'index=$index ; ${super .toString ()}' ;
122
+ String toString () => 'index=$index ; ${keepAlive == true ? "keepAlive; " : "" }${ super .toString ()}' ;
116
123
}
117
124
118
125
/// A sliver with multiple box children.
@@ -168,10 +175,15 @@ abstract class RenderSliverMultiBoxAdaptor extends RenderSliver
168
175
RenderSliverBoxChildManager get childManager => _childManager;
169
176
final RenderSliverBoxChildManager _childManager;
170
177
178
+ /// The nodes being kept alive despite not being visible.
179
+ final Map <int , RenderBox > _keepAliveBucket = < int , RenderBox > {};
180
+
171
181
@override
172
182
void adoptChild (RenderObject child) {
173
183
super .adoptChild (child);
174
- childManager.didAdoptChild (child);
184
+ final SliverMultiBoxAdaptorParentData childParentData = child.parentData;
185
+ if (! childParentData._keptAlive)
186
+ childManager.didAdoptChild (child);
175
187
}
176
188
177
189
bool _debugAssertChildListLocked () => childManager.debugAssertChildListLocked ();
@@ -192,64 +204,139 @@ abstract class RenderSliverMultiBoxAdaptor extends RenderSliver
192
204
});
193
205
}
194
206
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
+
195
288
/// Called during layout to create and add the child with the given index and
196
289
/// scroll offset.
197
290
///
198
291
/// 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] .
200
294
///
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.
203
297
///
204
298
/// Does not layout the new child.
205
299
///
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` .
209
304
@protected
210
305
bool addInitialChild ({ int index: 0 , double layoutOffset: 0.0 }) {
211
306
assert (_debugAssertChildListLocked ());
212
307
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 ;
229
318
}
230
319
231
320
/// Called during layout to create, add, and layout the child before
232
321
/// [firstChild] .
233
322
///
234
323
/// 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] .
236
326
///
237
- /// Returns the new child or null if no child is created .
327
+ /// Returns the new child or null if no child was obtained .
238
328
///
239
329
/// The child that was previously the first child, as well as any subsequent
240
330
/// children, may be removed by this call if they have not yet been laid out
241
331
/// 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` .
243
333
@protected
244
334
RenderBox insertAndLayoutLeadingChild (BoxConstraints childConstraints, {
245
335
bool parentUsesSize: false ,
246
336
}) {
247
337
assert (_debugAssertChildListLocked ());
248
338
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 );
253
340
if (indexOf (firstChild) == index) {
254
341
firstChild.layout (childConstraints, parentUsesSize: parentUsesSize);
255
342
return firstChild;
@@ -262,7 +349,8 @@ abstract class RenderSliverMultiBoxAdaptor extends RenderSliver
262
349
/// the given child.
263
350
///
264
351
/// 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] .
266
354
///
267
355
/// Returns the new child. It is the responsibility of the caller to configure
268
356
/// the child's scroll offset.
@@ -277,13 +365,9 @@ abstract class RenderSliverMultiBoxAdaptor extends RenderSliver
277
365
assert (_debugAssertChildListLocked ());
278
366
assert (after != null );
279
367
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);
284
369
final RenderBox child = childAfter (after);
285
370
if (child != null && indexOf (child) == index) {
286
- assert (indexOf (child) == index);
287
371
child.layout (childConstraints, parentUsesSize: parentUsesSize);
288
372
return child;
289
373
}
@@ -293,19 +377,37 @@ abstract class RenderSliverMultiBoxAdaptor extends RenderSliver
293
377
294
378
/// Called after layout with the number of children that can be garbage
295
379
/// 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.
296
387
@protected
297
388
void collectGarbage (int leadingGarbage, int trailingGarbage) {
298
389
assert (_debugAssertChildListLocked ());
299
390
assert (childCount >= leadingGarbage + trailingGarbage);
300
391
invokeLayoutCallback <SliverConstraints >((SliverConstraints constraints) {
301
392
while (leadingGarbage > 0 ) {
302
- _childManager. removeChild (firstChild);
393
+ _destroyOrCacheChild (firstChild);
303
394
leadingGarbage -= 1 ;
304
395
}
305
396
while (trailingGarbage > 0 ) {
306
- _childManager. removeChild (lastChild);
397
+ _destroyOrCacheChild (lastChild);
307
398
trailingGarbage -= 1 ;
308
399
}
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);
309
411
});
310
412
}
311
413
@@ -442,4 +544,42 @@ abstract class RenderSliverMultiBoxAdaptor extends RenderSliver
442
544
});
443
545
return true ;
444
546
}
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
+ }
445
585
}
0 commit comments