1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/// @docImport 'package:flutter/widgets.dart';
6///
7/// @docImport 'sliver_grid.dart';
8/// @docImport 'sliver_list.dart';
9library;
10
11import 'package:flutter/foundation.dart';
12import 'package:vector_math/vector_math_64.dart';
13
14import 'box.dart';
15import 'object.dart';
16import 'sliver.dart';
17import 'sliver_fixed_extent_list.dart';
18
19/// A delegate used by [RenderSliverMultiBoxAdaptor] to manage its children.
20///
21/// [RenderSliverMultiBoxAdaptor] objects reify their children lazily to avoid
22/// spending resources on children that are not visible in the viewport. This
23/// delegate lets these objects create and remove children as well as estimate
24/// the total scroll offset extent occupied by the full child list.
25abstract class RenderSliverBoxChildManager {
26 /// Called during layout when a new child is needed. The child should be
27 /// inserted into the child list in the appropriate position, after the
28 /// `after` child (at the start of the list if `after` is null). Its index and
29 /// scroll offsets will automatically be set appropriately.
30 ///
31 /// The `index` argument gives the index of the child to show. It is possible
32 /// for negative indices to be requested. For example: if the user scrolls
33 /// from child 0 to child 10, and then those children get much smaller, and
34 /// then the user scrolls back up again, this method will eventually be asked
35 /// to produce a child for index -1.
36 ///
37 /// If no child corresponds to `index`, then do nothing.
38 ///
39 /// Which child is indicated by index zero depends on the [GrowthDirection]
40 /// specified in the `constraints` of the [RenderSliverMultiBoxAdaptor]. For
41 /// example if the children are the alphabet, then if
42 /// [SliverConstraints.growthDirection] is [GrowthDirection.forward] then
43 /// index zero is A, and index 25 is Z. On the other hand if
44 /// [SliverConstraints.growthDirection] is [GrowthDirection.reverse] then
45 /// index zero is Z, and index 25 is A.
46 ///
47 /// During a call to [createChild] it is valid to remove other children from
48 /// the [RenderSliverMultiBoxAdaptor] object if they were not created during
49 /// this frame and have not yet been updated during this frame. It is not
50 /// valid to add any other children to this render object.
51 void createChild(int index, { required RenderBox? after });
52
53 /// Remove the given child from the child list.
54 ///
55 /// Called by [RenderSliverMultiBoxAdaptor.collectGarbage], which itself is
56 /// called from [RenderSliverMultiBoxAdaptor]'s `performLayout`.
57 ///
58 /// The index of the given child can be obtained using the
59 /// [RenderSliverMultiBoxAdaptor.indexOf] method, which reads it from the
60 /// [SliverMultiBoxAdaptorParentData.index] field of the child's
61 /// [RenderObject.parentData].
62 void removeChild(RenderBox child);
63
64 /// Called to estimate the total scrollable extents of this object.
65 ///
66 /// Must return the total distance from the start of the child with the
67 /// earliest possible index to the end of the child with the last possible
68 /// index.
69 double estimateMaxScrollOffset(
70 SliverConstraints constraints, {
71 int? firstIndex,
72 int? lastIndex,
73 double? leadingScrollOffset,
74 double? trailingScrollOffset,
75 });
76
77 /// Called to obtain a precise measure of the total number of children.
78 ///
79 /// Must return the number that is one greater than the greatest `index` for
80 /// which `createChild` will actually create a child.
81 ///
82 /// This is used when [createChild] cannot add a child for a positive `index`,
83 /// to determine the precise dimensions of the sliver. It must return an
84 /// accurate and precise non-null value. It will not be called if
85 /// [createChild] is always able to create a child (e.g. for an infinite
86 /// list).
87 int get childCount;
88
89 /// The best available estimate of [childCount], or null if no estimate is available.
90 ///
91 /// This differs from [childCount] in that [childCount] never returns null (and must
92 /// not be accessed if the child count is not yet available, meaning the [createChild]
93 /// method has not been provided an index that does not create a child).
94 ///
95 /// See also:
96 ///
97 /// * [SliverChildDelegate.estimatedChildCount], to which this getter defers.
98 int? get estimatedChildCount => null;
99
100 /// Called during [RenderSliverMultiBoxAdaptor.adoptChild] or
101 /// [RenderSliverMultiBoxAdaptor.move].
102 ///
103 /// Subclasses must ensure that the [SliverMultiBoxAdaptorParentData.index]
104 /// field of the child's [RenderObject.parentData] accurately reflects the
105 /// child's index in the child list after this function returns.
106 void didAdoptChild(RenderBox child);
107
108 /// Called during layout to indicate whether this object provided insufficient
109 /// children for the [RenderSliverMultiBoxAdaptor] to fill the
110 /// [SliverConstraints.remainingPaintExtent].
111 ///
112 /// Typically called unconditionally at the start of layout with false and
113 /// then later called with true when the [RenderSliverMultiBoxAdaptor]
114 /// fails to create a child required to fill the
115 /// [SliverConstraints.remainingPaintExtent].
116 ///
117 /// Useful for subclasses to determine whether newly added children could
118 /// affect the visible contents of the [RenderSliverMultiBoxAdaptor].
119 void setDidUnderflow(bool value);
120
121 /// Called at the beginning of layout to indicate that layout is about to
122 /// occur.
123 void didStartLayout() { }
124
125 /// Called at the end of layout to indicate that layout is now complete.
126 void didFinishLayout() { }
127
128 /// In debug mode, asserts that this manager is not expecting any
129 /// modifications to the [RenderSliverMultiBoxAdaptor]'s child list.
130 ///
131 /// This function always returns true.
132 ///
133 /// The manager is not required to track whether it is expecting modifications
134 /// to the [RenderSliverMultiBoxAdaptor]'s child list and can return
135 /// true without making any assertions.
136 bool debugAssertChildListLocked() => true;
137}
138
139/// Parent data structure used by [RenderSliverWithKeepAliveMixin].
140mixin KeepAliveParentDataMixin implements ParentData {
141 /// Whether to keep the child alive even when it is no longer visible.
142 bool keepAlive = false;
143
144 /// Whether the widget is currently being kept alive, i.e. has [keepAlive] set
145 /// to true and is offscreen.
146 bool get keptAlive;
147}
148
149/// This class exists to dissociate [KeepAlive] from [RenderSliverMultiBoxAdaptor].
150///
151/// [RenderSliverWithKeepAliveMixin.setupParentData] must be implemented to use
152/// a parentData class that uses the right mixin or whatever is appropriate.
153mixin RenderSliverWithKeepAliveMixin implements RenderSliver {
154 /// Alerts the developer that the child's parentData needs to be of type
155 /// [KeepAliveParentDataMixin].
156 @override
157 void setupParentData(RenderObject child) {
158 assert(child.parentData is KeepAliveParentDataMixin);
159 }
160}
161
162/// Parent data structure used by [RenderSliverMultiBoxAdaptor].
163class SliverMultiBoxAdaptorParentData extends SliverLogicalParentData with ContainerParentDataMixin<RenderBox>, KeepAliveParentDataMixin {
164 /// The index of this child according to the [RenderSliverBoxChildManager].
165 int? index;
166
167 @override
168 bool get keptAlive => _keptAlive;
169 bool _keptAlive = false;
170
171 @override
172 String toString() => 'index=$index; ${keepAlive ? "keepAlive; " : ""}${super.toString()}';
173}
174
175/// A sliver with multiple box children.
176///
177/// [RenderSliverMultiBoxAdaptor] is a base class for slivers that have multiple
178/// box children. The children are managed by a [RenderSliverBoxChildManager],
179/// which lets subclasses create children lazily during layout. Typically
180/// subclasses will create only those children that are actually needed to fill
181/// the [SliverConstraints.remainingPaintExtent].
182///
183/// The contract for adding and removing children from this render object is
184/// more strict than for normal render objects:
185///
186/// * Children can be removed except during a layout pass if they have already
187/// been laid out during that layout pass.
188/// * Children cannot be added except during a call to [childManager], and
189/// then only if there is no child corresponding to that index (or the child
190/// corresponding to that index was first removed).
191///
192/// See also:
193///
194/// * [RenderSliverToBoxAdapter], which has a single box child.
195/// * [RenderSliverList], which places its children in a linear
196/// array.
197/// * [RenderSliverFixedExtentList], which places its children in a linear
198/// array with a fixed extent in the main axis.
199/// * [RenderSliverGrid], which places its children in arbitrary positions.
200abstract class RenderSliverMultiBoxAdaptor extends RenderSliver
201 with ContainerRenderObjectMixin<RenderBox, SliverMultiBoxAdaptorParentData>,
202 RenderSliverHelpers, RenderSliverWithKeepAliveMixin {
203
204 /// Creates a sliver with multiple box children.
205 RenderSliverMultiBoxAdaptor({
206 required RenderSliverBoxChildManager childManager,
207 }) : _childManager = childManager {
208 assert(() {
209 _debugDanglingKeepAlives = <RenderBox>[];
210 return true;
211 }());
212 }
213
214 @override
215 void setupParentData(RenderObject child) {
216 if (child.parentData is! SliverMultiBoxAdaptorParentData) {
217 child.parentData = SliverMultiBoxAdaptorParentData();
218 }
219 }
220
221 /// The delegate that manages the children of this object.
222 ///
223 /// Rather than having a concrete list of children, a
224 /// [RenderSliverMultiBoxAdaptor] uses a [RenderSliverBoxChildManager] to
225 /// create children during layout in order to fill the
226 /// [SliverConstraints.remainingPaintExtent].
227 @protected
228 RenderSliverBoxChildManager get childManager => _childManager;
229 final RenderSliverBoxChildManager _childManager;
230
231 /// The nodes being kept alive despite not being visible.
232 final Map<int, RenderBox> _keepAliveBucket = <int, RenderBox>{};
233
234 late List<RenderBox> _debugDanglingKeepAlives;
235
236 /// Indicates whether integrity check is enabled.
237 ///
238 /// Setting this property to true will immediately perform an integrity check.
239 ///
240 /// The integrity check consists of:
241 ///
242 /// 1. Verify that the children index in childList is in ascending order.
243 /// 2. Verify that there is no dangling keepalive child as the result of [move].
244 bool get debugChildIntegrityEnabled => _debugChildIntegrityEnabled;
245 bool _debugChildIntegrityEnabled = true;
246 set debugChildIntegrityEnabled(bool enabled) {
247 assert(() {
248 _debugChildIntegrityEnabled = enabled;
249 return _debugVerifyChildOrder() &&
250 (!_debugChildIntegrityEnabled || _debugDanglingKeepAlives.isEmpty);
251 }());
252 }
253
254 @override
255 void adoptChild(RenderObject child) {
256 super.adoptChild(child);
257 final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData;
258 if (!childParentData._keptAlive) {
259 childManager.didAdoptChild(child as RenderBox);
260 }
261 }
262
263 bool _debugAssertChildListLocked() => childManager.debugAssertChildListLocked();
264
265 /// Verify that the child list index is in strictly increasing order.
266 ///
267 /// This has no effect in release builds.
268 bool _debugVerifyChildOrder() {
269 if (_debugChildIntegrityEnabled) {
270 RenderBox? child = firstChild;
271 int index;
272 while (child != null) {
273 index = indexOf(child);
274 child = childAfter(child);
275 assert(child == null || indexOf(child) > index);
276 }
277 }
278 return true;
279 }
280
281 @override
282 void insert(RenderBox child, { RenderBox? after }) {
283 assert(!_keepAliveBucket.containsValue(child));
284 super.insert(child, after: after);
285 assert(firstChild != null);
286 assert(_debugVerifyChildOrder());
287 }
288
289 @override
290 void move(RenderBox child, { RenderBox? after }) {
291 // There are two scenarios:
292 //
293 // 1. The child is not keptAlive.
294 // The child is in the childList maintained by ContainerRenderObjectMixin.
295 // We can call super.move and update parentData with the new slot.
296 //
297 // 2. The child is keptAlive.
298 // In this case, the child is no longer in the childList but might be stored in
299 // [_keepAliveBucket]. We need to update the location of the child in the bucket.
300 final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData;
301 if (!childParentData.keptAlive) {
302 super.move(child, after: after);
303 childManager.didAdoptChild(child); // updates the slot in the parentData
304 // Its slot may change even if super.move does not change the position.
305 // In this case, we still want to mark as needs layout.
306 markNeedsLayout();
307 } else {
308 // If the child in the bucket is not current child, that means someone has
309 // already moved and replaced current child, and we cannot remove this child.
310 if (_keepAliveBucket[childParentData.index] == child) {
311 _keepAliveBucket.remove(childParentData.index);
312 }
313 assert(() {
314 _debugDanglingKeepAlives.remove(child);
315 return true;
316 }());
317 // Update the slot and reinsert back to _keepAliveBucket in the new slot.
318 childManager.didAdoptChild(child);
319 // If there is an existing child in the new slot, that mean that child will
320 // be moved to other index. In other cases, the existing child should have been
321 // removed by updateChild. Thus, it is ok to overwrite it.
322 assert(() {
323 if (_keepAliveBucket.containsKey(childParentData.index)) {
324 _debugDanglingKeepAlives.add(_keepAliveBucket[childParentData.index]!);
325 }
326 return true;
327 }());
328 _keepAliveBucket[childParentData.index!] = child;
329 }
330 }
331
332 @override
333 void remove(RenderBox child) {
334 final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData;
335 if (!childParentData._keptAlive) {
336 super.remove(child);
337 return;
338 }
339 assert(_keepAliveBucket[childParentData.index] == child);
340 assert(() {
341 _debugDanglingKeepAlives.remove(child);
342 return true;
343 }());
344 _keepAliveBucket.remove(childParentData.index);
345 dropChild(child);
346 }
347
348 @override
349 void removeAll() {
350 super.removeAll();
351 _keepAliveBucket.values.forEach(dropChild);
352 _keepAliveBucket.clear();
353 }
354
355 void _createOrObtainChild(int index, { required RenderBox? after }) {
356 invokeLayoutCallback<SliverConstraints>((SliverConstraints constraints) {
357 assert(constraints == this.constraints);
358 if (_keepAliveBucket.containsKey(index)) {
359 final RenderBox child = _keepAliveBucket.remove(index)!;
360 final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData;
361 assert(childParentData._keptAlive);
362 dropChild(child);
363 child.parentData = childParentData;
364 insert(child, after: after);
365 childParentData._keptAlive = false;
366 } else {
367 _childManager.createChild(index, after: after);
368 }
369 });
370 }
371
372 void _destroyOrCacheChild(RenderBox child) {
373 final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData;
374 if (childParentData.keepAlive) {
375 assert(!childParentData._keptAlive);
376 remove(child);
377 _keepAliveBucket[childParentData.index!] = child;
378 child.parentData = childParentData;
379 super.adoptChild(child);
380 childParentData._keptAlive = true;
381 } else {
382 assert(child.parent == this);
383 _childManager.removeChild(child);
384 assert(child.parent == null);
385 }
386 }
387
388 @override
389 void attach(PipelineOwner owner) {
390 super.attach(owner);
391 for (final RenderBox child in _keepAliveBucket.values) {
392 child.attach(owner);
393 }
394 }
395
396 @override
397 void detach() {
398 super.detach();
399 for (final RenderBox child in _keepAliveBucket.values) {
400 child.detach();
401 }
402 }
403
404 @override
405 void redepthChildren() {
406 super.redepthChildren();
407 _keepAliveBucket.values.forEach(redepthChild);
408 }
409
410 @override
411 void visitChildren(RenderObjectVisitor visitor) {
412 super.visitChildren(visitor);
413 _keepAliveBucket.values.forEach(visitor);
414 }
415
416 @override
417 void visitChildrenForSemantics(RenderObjectVisitor visitor) {
418 super.visitChildren(visitor);
419 // Do not visit children in [_keepAliveBucket].
420 }
421
422 /// Called during layout to create and add the child with the given index and
423 /// scroll offset.
424 ///
425 /// Calls [RenderSliverBoxChildManager.createChild] to actually create and add
426 /// the child if necessary. The child may instead be obtained from a cache;
427 /// see [SliverMultiBoxAdaptorParentData.keepAlive].
428 ///
429 /// Returns false if there was no cached child and `createChild` did not add
430 /// any child, otherwise returns true.
431 ///
432 /// Does not layout the new child.
433 ///
434 /// When this is called, there are no visible children, so no children can be
435 /// removed during the call to `createChild`. No child should be added during
436 /// that call either, except for the one that is created and returned by
437 /// `createChild`.
438 @protected
439 bool addInitialChild({ int index = 0, double layoutOffset = 0.0 }) {
440 assert(_debugAssertChildListLocked());
441 assert(firstChild == null);
442 _createOrObtainChild(index, after: null);
443 if (firstChild != null) {
444 assert(firstChild == lastChild);
445 assert(indexOf(firstChild!) == index);
446 final SliverMultiBoxAdaptorParentData firstChildParentData = firstChild!.parentData! as SliverMultiBoxAdaptorParentData;
447 firstChildParentData.layoutOffset = layoutOffset;
448 return true;
449 }
450 childManager.setDidUnderflow(true);
451 return false;
452 }
453
454 /// Called during layout to create, add, and layout the child before
455 /// [firstChild].
456 ///
457 /// Calls [RenderSliverBoxChildManager.createChild] to actually create and add
458 /// the child if necessary. The child may instead be obtained from a cache;
459 /// see [SliverMultiBoxAdaptorParentData.keepAlive].
460 ///
461 /// Returns the new child or null if no child was obtained.
462 ///
463 /// The child that was previously the first child, as well as any subsequent
464 /// children, may be removed by this call if they have not yet been laid out
465 /// during this layout pass. No child should be added during that call except
466 /// for the one that is created and returned by `createChild`.
467 @protected
468 RenderBox? insertAndLayoutLeadingChild(
469 BoxConstraints childConstraints, {
470 bool parentUsesSize = false,
471 }) {
472 assert(_debugAssertChildListLocked());
473 final int index = indexOf(firstChild!) - 1;
474 _createOrObtainChild(index, after: null);
475 if (indexOf(firstChild!) == index) {
476 firstChild!.layout(childConstraints, parentUsesSize: parentUsesSize);
477 return firstChild;
478 }
479 childManager.setDidUnderflow(true);
480 return null;
481 }
482
483 /// Called during layout to create, add, and layout the child after
484 /// the given child.
485 ///
486 /// Calls [RenderSliverBoxChildManager.createChild] to actually create and add
487 /// the child if necessary. The child may instead be obtained from a cache;
488 /// see [SliverMultiBoxAdaptorParentData.keepAlive].
489 ///
490 /// Returns the new child. It is the responsibility of the caller to configure
491 /// the child's scroll offset.
492 ///
493 /// Children after the `after` child may be removed in the process. Only the
494 /// new child may be added.
495 @protected
496 RenderBox? insertAndLayoutChild(
497 BoxConstraints childConstraints, {
498 required RenderBox? after,
499 bool parentUsesSize = false,
500 }) {
501 assert(_debugAssertChildListLocked());
502 assert(after != null);
503 final int index = indexOf(after!) + 1;
504 _createOrObtainChild(index, after: after);
505 final RenderBox? child = childAfter(after);
506 if (child != null && indexOf(child) == index) {
507 child.layout(childConstraints, parentUsesSize: parentUsesSize);
508 return child;
509 }
510 childManager.setDidUnderflow(true);
511 return null;
512 }
513
514 /// Returns the number of children preceding the `firstIndex` that need to be
515 /// garbage collected.
516 ///
517 /// See also:
518 ///
519 /// * [collectGarbage], which takes the leading and trailing number of
520 /// children to be garbage collected.
521 /// * [calculateTrailingGarbage], which similarly returns the number of
522 /// trailing children to be garbage collected.
523 @visibleForTesting
524 @protected
525 int calculateLeadingGarbage({required int firstIndex}) {
526 RenderBox? walker = firstChild;
527 int leadingGarbage = 0;
528 while (walker != null && indexOf(walker) < firstIndex) {
529 leadingGarbage += 1;
530 walker = childAfter(walker);
531 }
532 return leadingGarbage;
533 }
534
535 /// Returns the number of children following the `lastIndex` that need to be
536 /// garbage collected.
537 ///
538 /// See also:
539 ///
540 /// * [collectGarbage], which takes the leading and trailing number of
541 /// children to be garbage collected.
542 /// * [calculateLeadingGarbage], which similarly returns the number of
543 /// leading children to be garbage collected.
544 @visibleForTesting
545 @protected
546 int calculateTrailingGarbage({required int lastIndex}) {
547 RenderBox? walker = lastChild;
548 int trailingGarbage = 0;
549 while (walker != null && indexOf(walker) > lastIndex) {
550 trailingGarbage += 1;
551 walker = childBefore(walker);
552 }
553 return trailingGarbage;
554 }
555
556 /// Called after layout with the number of children that can be garbage
557 /// collected at the head and tail of the child list.
558 ///
559 /// Children whose [SliverMultiBoxAdaptorParentData.keepAlive] property is
560 /// set to true will be removed to a cache instead of being dropped.
561 ///
562 /// This method also collects any children that were previously kept alive but
563 /// are now no longer necessary. As such, it should be called every time
564 /// [performLayout] is run, even if the arguments are both zero.
565 ///
566 /// See also:
567 ///
568 /// * [calculateLeadingGarbage], which can be used to determine
569 /// `leadingGarbage` here.
570 /// * [calculateTrailingGarbage], which can be used to determine
571 /// `trailingGarbage` here.
572 @protected
573 void collectGarbage(int leadingGarbage, int trailingGarbage) {
574 assert(_debugAssertChildListLocked());
575 assert(childCount >= leadingGarbage + trailingGarbage);
576 invokeLayoutCallback<SliverConstraints>((SliverConstraints constraints) {
577 while (leadingGarbage > 0) {
578 _destroyOrCacheChild(firstChild!);
579 leadingGarbage -= 1;
580 }
581 while (trailingGarbage > 0) {
582 _destroyOrCacheChild(lastChild!);
583 trailingGarbage -= 1;
584 }
585 // Ask the child manager to remove the children that are no longer being
586 // kept alive. (This should cause _keepAliveBucket to change, so we have
587 // to prepare our list ahead of time.)
588 _keepAliveBucket.values.where((RenderBox child) {
589 final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData;
590 return !childParentData.keepAlive;
591 }).toList().forEach(_childManager.removeChild);
592 assert(_keepAliveBucket.values.where((RenderBox child) {
593 final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData;
594 return !childParentData.keepAlive;
595 }).isEmpty);
596 });
597 }
598
599 /// Returns the index of the given child, as given by the
600 /// [SliverMultiBoxAdaptorParentData.index] field of the child's [parentData].
601 int indexOf(RenderBox child) {
602 final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData;
603 assert(childParentData.index != null);
604 return childParentData.index!;
605 }
606
607 /// Returns the dimension of the given child in the main axis, as given by the
608 /// child's [RenderBox.size] property. This is only valid after layout.
609 @protected
610 double paintExtentOf(RenderBox child) {
611 assert(child.hasSize);
612 return switch (constraints.axis) {
613 Axis.horizontal => child.size.width,
614 Axis.vertical => child.size.height,
615 };
616 }
617
618 @override
619 bool hitTestChildren(SliverHitTestResult result, { required double mainAxisPosition, required double crossAxisPosition }) {
620 RenderBox? child = lastChild;
621 final BoxHitTestResult boxResult = BoxHitTestResult.wrap(result);
622 while (child != null) {
623 if (hitTestBoxChild(boxResult, child, mainAxisPosition: mainAxisPosition, crossAxisPosition: crossAxisPosition)) {
624 return true;
625 }
626 child = childBefore(child);
627 }
628 return false;
629 }
630
631 @override
632 double childMainAxisPosition(RenderBox child) {
633 return childScrollOffset(child)! - constraints.scrollOffset;
634 }
635
636 @override
637 double? childScrollOffset(RenderObject child) {
638 assert(child.parent == this);
639 final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData;
640 return childParentData.layoutOffset;
641 }
642
643 @override
644 bool paintsChild(RenderBox child) {
645 final SliverMultiBoxAdaptorParentData? childParentData = child.parentData as SliverMultiBoxAdaptorParentData?;
646 return childParentData?.index != null &&
647 !_keepAliveBucket.containsKey(childParentData!.index);
648 }
649
650 @override
651 void applyPaintTransform(RenderBox child, Matrix4 transform) {
652 if (!paintsChild(child)) {
653 // This can happen if some child asks for the global transform even though
654 // they are not getting painted. In that case, the transform sets set to
655 // zero since [applyPaintTransformForBoxChild] would end up throwing due
656 // to the child not being configured correctly for applying a transform.
657 // There's no assert here because asking for the paint transform is a
658 // valid thing to do even if a child would not be painted, but there is no
659 // meaningful non-zero matrix to use in this case.
660 transform.setZero();
661 } else {
662 applyPaintTransformForBoxChild(child, transform);
663 }
664 }
665
666 @override
667 void paint(PaintingContext context, Offset offset) {
668 if (firstChild == null) {
669 return;
670 }
671 // offset is to the top-left corner, regardless of our axis direction.
672 // originOffset gives us the delta from the real origin to the origin in the axis direction.
673 final Offset mainAxisUnit, crossAxisUnit, originOffset;
674 final bool addExtent;
675 switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
676 case AxisDirection.up:
677 mainAxisUnit = const Offset(0.0, -1.0);
678 crossAxisUnit = const Offset(1.0, 0.0);
679 originOffset = offset + Offset(0.0, geometry!.paintExtent);
680 addExtent = true;
681 case AxisDirection.right:
682 mainAxisUnit = const Offset(1.0, 0.0);
683 crossAxisUnit = const Offset(0.0, 1.0);
684 originOffset = offset;
685 addExtent = false;
686 case AxisDirection.down:
687 mainAxisUnit = const Offset(0.0, 1.0);
688 crossAxisUnit = const Offset(1.0, 0.0);
689 originOffset = offset;
690 addExtent = false;
691 case AxisDirection.left:
692 mainAxisUnit = const Offset(-1.0, 0.0);
693 crossAxisUnit = const Offset(0.0, 1.0);
694 originOffset = offset + Offset(geometry!.paintExtent, 0.0);
695 addExtent = true;
696 }
697 RenderBox? child = firstChild;
698 while (child != null) {
699 final double mainAxisDelta = childMainAxisPosition(child);
700 final double crossAxisDelta = childCrossAxisPosition(child);
701 Offset childOffset = Offset(
702 originOffset.dx + mainAxisUnit.dx * mainAxisDelta + crossAxisUnit.dx * crossAxisDelta,
703 originOffset.dy + mainAxisUnit.dy * mainAxisDelta + crossAxisUnit.dy * crossAxisDelta,
704 );
705 if (addExtent) {
706 childOffset += mainAxisUnit * paintExtentOf(child);
707 }
708
709 // If the child's visible interval (mainAxisDelta, mainAxisDelta + paintExtentOf(child))
710 // does not intersect the paint extent interval (0, constraints.remainingPaintExtent), it's hidden.
711 if (mainAxisDelta < constraints.remainingPaintExtent && mainAxisDelta + paintExtentOf(child) > 0) {
712 context.paintChild(child, childOffset);
713 }
714
715 child = childAfter(child);
716 }
717 }
718
719 @override
720 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
721 super.debugFillProperties(properties);
722 properties.add(DiagnosticsNode.message(firstChild != null ? 'currently live children: ${indexOf(firstChild!)} to ${indexOf(lastChild!)}' : 'no children current live'));
723 }
724
725 /// Asserts that the reified child list is not empty and has a contiguous
726 /// sequence of indices.
727 ///
728 /// Always returns true.
729 bool debugAssertChildListIsNonEmptyAndContiguous() {
730 assert(() {
731 assert(firstChild != null);
732 int index = indexOf(firstChild!);
733 RenderBox? child = childAfter(firstChild!);
734 while (child != null) {
735 index += 1;
736 assert(indexOf(child) == index);
737 child = childAfter(child);
738 }
739 return true;
740 }());
741 return true;
742 }
743
744 @override
745 List<DiagnosticsNode> debugDescribeChildren() {
746 final List<DiagnosticsNode> children = <DiagnosticsNode>[];
747 if (firstChild != null) {
748 RenderBox? child = firstChild;
749 while (true) {
750 final SliverMultiBoxAdaptorParentData childParentData = child!.parentData! as SliverMultiBoxAdaptorParentData;
751 children.add(child.toDiagnosticsNode(name: 'child with index ${childParentData.index}'));
752 if (child == lastChild) {
753 break;
754 }
755 child = childParentData.nextSibling;
756 }
757 }
758 if (_keepAliveBucket.isNotEmpty) {
759 final List<int> indices = _keepAliveBucket.keys.toList()..sort();
760 for (final int index in indices) {
761 children.add(_keepAliveBucket[index]!.toDiagnosticsNode(
762 name: 'child with index $index (kept alive but not laid out)',
763 style: DiagnosticsTreeStyle.offstage,
764 ));
765 }
766 }
767 return children;
768 }
769}
770