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
164 with ContainerParentDataMixin<RenderBox>, KeepAliveParentDataMixin {
165 /// The index of this child according to the [RenderSliverBoxChildManager].
166 int? index;
167
168 @override
169 bool get keptAlive => _keptAlive;
170 bool _keptAlive = false;
171
172 @override
173 String toString() => 'index=$index; ${keepAlive ? "keepAlive; " : ""}${super.toString()}';
174}
175
176/// A sliver with multiple box children.
177///
178/// [RenderSliverMultiBoxAdaptor] is a base class for slivers that have multiple
179/// box children. The children are managed by a [RenderSliverBoxChildManager],
180/// which lets subclasses create children lazily during layout. Typically
181/// subclasses will create only those children that are actually needed to fill
182/// the [SliverConstraints.remainingPaintExtent].
183///
184/// The contract for adding and removing children from this render object is
185/// more strict than for normal render objects:
186///
187/// * Children can be removed except during a layout pass if they have already
188/// been laid out during that layout pass.
189/// * Children cannot be added except during a call to [childManager], and
190/// then only if there is no child corresponding to that index (or the child
191/// corresponding to that index was first removed).
192///
193/// See also:
194///
195/// * [RenderSliverToBoxAdapter], which has a single box child.
196/// * [RenderSliverList], which places its children in a linear
197/// array.
198/// * [RenderSliverFixedExtentList], which places its children in a linear
199/// array with a fixed extent in the main axis.
200/// * [RenderSliverGrid], which places its children in arbitrary positions.
201abstract class RenderSliverMultiBoxAdaptor extends RenderSliver
202 with
203 ContainerRenderObjectMixin<RenderBox, SliverMultiBoxAdaptorParentData>,
204 RenderSliverHelpers,
205 RenderSliverWithKeepAliveMixin {
206 /// Creates a sliver with multiple box children.
207 RenderSliverMultiBoxAdaptor({required RenderSliverBoxChildManager childManager})
208 : _childManager = childManager {
209 assert(() {
210 _debugDanglingKeepAlives = <RenderBox>[];
211 return true;
212 }());
213 }
214
215 @override
216 void setupParentData(RenderObject child) {
217 if (child.parentData is! SliverMultiBoxAdaptorParentData) {
218 child.parentData = SliverMultiBoxAdaptorParentData();
219 }
220 }
221
222 /// The delegate that manages the children of this object.
223 ///
224 /// Rather than having a concrete list of children, a
225 /// [RenderSliverMultiBoxAdaptor] uses a [RenderSliverBoxChildManager] to
226 /// create children during layout in order to fill the
227 /// [SliverConstraints.remainingPaintExtent].
228 @protected
229 RenderSliverBoxChildManager get childManager => _childManager;
230 final RenderSliverBoxChildManager _childManager;
231
232 /// The nodes being kept alive despite not being visible.
233 final Map<int, RenderBox> _keepAliveBucket = <int, RenderBox>{};
234
235 late List<RenderBox> _debugDanglingKeepAlives;
236
237 /// Indicates whether integrity check is enabled.
238 ///
239 /// Setting this property to true will immediately perform an integrity check.
240 ///
241 /// The integrity check consists of:
242 ///
243 /// 1. Verify that the children index in childList is in ascending order.
244 /// 2. Verify that there is no dangling keepalive child as the result of [move].
245 bool get debugChildIntegrityEnabled => _debugChildIntegrityEnabled;
246 bool _debugChildIntegrityEnabled = true;
247 set debugChildIntegrityEnabled(bool enabled) {
248 assert(() {
249 _debugChildIntegrityEnabled = enabled;
250 return _debugVerifyChildOrder() &&
251 (!_debugChildIntegrityEnabled || _debugDanglingKeepAlives.isEmpty);
252 }());
253 }
254
255 @override
256 void adoptChild(RenderObject child) {
257 super.adoptChild(child);
258 final SliverMultiBoxAdaptorParentData childParentData =
259 child.parentData! as SliverMultiBoxAdaptorParentData;
260 if (!childParentData._keptAlive) {
261 childManager.didAdoptChild(child as RenderBox);
262 }
263 }
264
265 bool _debugAssertChildListLocked() => childManager.debugAssertChildListLocked();
266
267 /// Verify that the child list index is in strictly increasing order.
268 ///
269 /// This has no effect in release builds.
270 bool _debugVerifyChildOrder() {
271 if (_debugChildIntegrityEnabled) {
272 RenderBox? child = firstChild;
273 int index;
274 while (child != null) {
275 index = indexOf(child);
276 child = childAfter(child);
277 assert(child == null || indexOf(child) > index);
278 }
279 }
280 return true;
281 }
282
283 @override
284 void insert(RenderBox child, {RenderBox? after}) {
285 assert(!_keepAliveBucket.containsValue(child));
286 super.insert(child, after: after);
287 assert(firstChild != null);
288 assert(_debugVerifyChildOrder());
289 }
290
291 @override
292 void move(RenderBox child, {RenderBox? after}) {
293 // There are two scenarios:
294 //
295 // 1. The child is not keptAlive.
296 // The child is in the childList maintained by ContainerRenderObjectMixin.
297 // We can call super.move and update parentData with the new slot.
298 //
299 // 2. The child is keptAlive.
300 // In this case, the child is no longer in the childList but might be stored in
301 // [_keepAliveBucket]. We need to update the location of the child in the bucket.
302 final SliverMultiBoxAdaptorParentData childParentData =
303 child.parentData! as SliverMultiBoxAdaptorParentData;
304 if (!childParentData.keptAlive) {
305 super.move(child, after: after);
306 childManager.didAdoptChild(child); // updates the slot in the parentData
307 // Its slot may change even if super.move does not change the position.
308 // In this case, we still want to mark as needs layout.
309 markNeedsLayout();
310 } else {
311 // If the child in the bucket is not current child, that means someone has
312 // already moved and replaced current child, and we cannot remove this child.
313 if (_keepAliveBucket[childParentData.index] == child) {
314 _keepAliveBucket.remove(childParentData.index);
315 }
316 assert(() {
317 _debugDanglingKeepAlives.remove(child);
318 return true;
319 }());
320 // Update the slot and reinsert back to _keepAliveBucket in the new slot.
321 childManager.didAdoptChild(child);
322 // If there is an existing child in the new slot, that mean that child will
323 // be moved to other index. In other cases, the existing child should have been
324 // removed by updateChild. Thus, it is ok to overwrite it.
325 assert(() {
326 if (_keepAliveBucket.containsKey(childParentData.index)) {
327 _debugDanglingKeepAlives.add(_keepAliveBucket[childParentData.index]!);
328 }
329 return true;
330 }());
331 _keepAliveBucket[childParentData.index!] = child;
332 }
333 }
334
335 @override
336 void remove(RenderBox child) {
337 final SliverMultiBoxAdaptorParentData childParentData =
338 child.parentData! as SliverMultiBoxAdaptorParentData;
339 if (!childParentData._keptAlive) {
340 super.remove(child);
341 return;
342 }
343 assert(_keepAliveBucket[childParentData.index] == child);
344 assert(() {
345 _debugDanglingKeepAlives.remove(child);
346 return true;
347 }());
348 _keepAliveBucket.remove(childParentData.index);
349 dropChild(child);
350 }
351
352 @override
353 void removeAll() {
354 super.removeAll();
355 _keepAliveBucket.values.forEach(dropChild);
356 _keepAliveBucket.clear();
357 }
358
359 void _createOrObtainChild(int index, {required RenderBox? after}) {
360 invokeLayoutCallback<SliverConstraints>((SliverConstraints constraints) {
361 assert(constraints == this.constraints);
362 if (_keepAliveBucket.containsKey(index)) {
363 final RenderBox child = _keepAliveBucket.remove(index)!;
364 final SliverMultiBoxAdaptorParentData childParentData =
365 child.parentData! as SliverMultiBoxAdaptorParentData;
366 assert(childParentData._keptAlive);
367 dropChild(child);
368 child.parentData = childParentData;
369 insert(child, after: after);
370 childParentData._keptAlive = false;
371 } else {
372 _childManager.createChild(index, after: after);
373 }
374 });
375 }
376
377 void _destroyOrCacheChild(RenderBox child) {
378 final SliverMultiBoxAdaptorParentData childParentData =
379 child.parentData! as SliverMultiBoxAdaptorParentData;
380 if (childParentData.keepAlive) {
381 assert(!childParentData._keptAlive);
382 remove(child);
383 _keepAliveBucket[childParentData.index!] = child;
384 child.parentData = childParentData;
385 super.adoptChild(child);
386 childParentData._keptAlive = true;
387 } else {
388 assert(child.parent == this);
389 _childManager.removeChild(child);
390 assert(child.parent == null);
391 }
392 }
393
394 @override
395 void attach(PipelineOwner owner) {
396 super.attach(owner);
397 for (final RenderBox child in _keepAliveBucket.values) {
398 child.attach(owner);
399 }
400 }
401
402 @override
403 void detach() {
404 super.detach();
405 for (final RenderBox child in _keepAliveBucket.values) {
406 child.detach();
407 }
408 }
409
410 @override
411 void redepthChildren() {
412 super.redepthChildren();
413 _keepAliveBucket.values.forEach(redepthChild);
414 }
415
416 @override
417 void visitChildren(RenderObjectVisitor visitor) {
418 super.visitChildren(visitor);
419 _keepAliveBucket.values.forEach(visitor);
420 }
421
422 @override
423 void visitChildrenForSemantics(RenderObjectVisitor visitor) {
424 super.visitChildren(visitor);
425 // Do not visit children in [_keepAliveBucket].
426 }
427
428 @override
429 Rect get semanticBounds {
430 // If we laid out the first child but this sliver is not visible, we report the
431 // semantic bounds of this sliver as the bounds of the first child. This is necessary
432 // for accessibility technologies to reach this sliver even when it is outside
433 // the current viewport and cache extent.
434 if (geometry != null && !geometry!.visible && firstChild != null && firstChild!.hasSize) {
435 return firstChild!.paintBounds;
436 }
437 return super.semanticBounds;
438 }
439
440 /// Called during layout to create and add the child with the given index and
441 /// scroll offset.
442 ///
443 /// Calls [RenderSliverBoxChildManager.createChild] to actually create and add
444 /// the child if necessary. The child may instead be obtained from a cache;
445 /// see [SliverMultiBoxAdaptorParentData.keepAlive].
446 ///
447 /// Returns false if there was no cached child and `createChild` did not add
448 /// any child, otherwise returns true.
449 ///
450 /// Does not layout the new child.
451 ///
452 /// When this is called, there are no visible children, so no children can be
453 /// removed during the call to `createChild`. No child should be added during
454 /// that call either, except for the one that is created and returned by
455 /// `createChild`.
456 @protected
457 bool addInitialChild({int index = 0, double layoutOffset = 0.0}) {
458 assert(_debugAssertChildListLocked());
459 assert(firstChild == null);
460 _createOrObtainChild(index, after: null);
461 if (firstChild != null) {
462 assert(firstChild == lastChild);
463 assert(indexOf(firstChild!) == index);
464 final SliverMultiBoxAdaptorParentData firstChildParentData =
465 firstChild!.parentData! as SliverMultiBoxAdaptorParentData;
466 firstChildParentData.layoutOffset = layoutOffset;
467 return true;
468 }
469 childManager.setDidUnderflow(true);
470 return false;
471 }
472
473 /// Called during layout to create, add, and layout the child before
474 /// [firstChild].
475 ///
476 /// Calls [RenderSliverBoxChildManager.createChild] to actually create and add
477 /// the child if necessary. The child may instead be obtained from a cache;
478 /// see [SliverMultiBoxAdaptorParentData.keepAlive].
479 ///
480 /// Returns the new child or null if no child was obtained.
481 ///
482 /// The child that was previously the first child, as well as any subsequent
483 /// children, may be removed by this call if they have not yet been laid out
484 /// during this layout pass. No child should be added during that call except
485 /// for the one that is created and returned by `createChild`.
486 @protected
487 RenderBox? insertAndLayoutLeadingChild(
488 BoxConstraints childConstraints, {
489 bool parentUsesSize = false,
490 }) {
491 assert(_debugAssertChildListLocked());
492 final int index = indexOf(firstChild!) - 1;
493 _createOrObtainChild(index, after: null);
494 if (indexOf(firstChild!) == index) {
495 firstChild!.layout(childConstraints, parentUsesSize: parentUsesSize);
496 return firstChild;
497 }
498 childManager.setDidUnderflow(true);
499 return null;
500 }
501
502 /// Called during layout to create, add, and layout the child after
503 /// the given child.
504 ///
505 /// Calls [RenderSliverBoxChildManager.createChild] to actually create and add
506 /// the child if necessary. The child may instead be obtained from a cache;
507 /// see [SliverMultiBoxAdaptorParentData.keepAlive].
508 ///
509 /// Returns the new child. It is the responsibility of the caller to configure
510 /// the child's scroll offset.
511 ///
512 /// Children after the `after` child may be removed in the process. Only the
513 /// new child may be added.
514 @protected
515 RenderBox? insertAndLayoutChild(
516 BoxConstraints childConstraints, {
517 required RenderBox? after,
518 bool parentUsesSize = false,
519 }) {
520 assert(_debugAssertChildListLocked());
521 assert(after != null);
522 final int index = indexOf(after!) + 1;
523 _createOrObtainChild(index, after: after);
524 final RenderBox? child = childAfter(after);
525 if (child != null && indexOf(child) == index) {
526 child.layout(childConstraints, parentUsesSize: parentUsesSize);
527 return child;
528 }
529 childManager.setDidUnderflow(true);
530 return null;
531 }
532
533 /// Returns the number of children preceding the `firstIndex` that need to be
534 /// garbage collected.
535 ///
536 /// See also:
537 ///
538 /// * [collectGarbage], which takes the leading and trailing number of
539 /// children to be garbage collected.
540 /// * [calculateTrailingGarbage], which similarly returns the number of
541 /// trailing children to be garbage collected.
542 @visibleForTesting
543 @protected
544 int calculateLeadingGarbage({required int firstIndex}) {
545 RenderBox? walker = firstChild;
546 int leadingGarbage = 0;
547 while (walker != null && indexOf(walker) < firstIndex) {
548 leadingGarbage += 1;
549 walker = childAfter(walker);
550 }
551 return leadingGarbage;
552 }
553
554 /// Returns the number of children following the `lastIndex` that need to be
555 /// garbage collected.
556 ///
557 /// See also:
558 ///
559 /// * [collectGarbage], which takes the leading and trailing number of
560 /// children to be garbage collected.
561 /// * [calculateLeadingGarbage], which similarly returns the number of
562 /// leading children to be garbage collected.
563 @visibleForTesting
564 @protected
565 int calculateTrailingGarbage({required int lastIndex}) {
566 RenderBox? walker = lastChild;
567 int trailingGarbage = 0;
568 while (walker != null && indexOf(walker) > lastIndex) {
569 trailingGarbage += 1;
570 walker = childBefore(walker);
571 }
572 return trailingGarbage;
573 }
574
575 /// Called after layout with the number of children that can be garbage
576 /// collected at the head and tail of the child list.
577 ///
578 /// Children whose [SliverMultiBoxAdaptorParentData.keepAlive] property is
579 /// set to true will be removed to a cache instead of being dropped.
580 ///
581 /// This method also collects any children that were previously kept alive but
582 /// are now no longer necessary. As such, it should be called every time
583 /// [performLayout] is run, even if the arguments are both zero.
584 ///
585 /// See also:
586 ///
587 /// * [calculateLeadingGarbage], which can be used to determine
588 /// `leadingGarbage` here.
589 /// * [calculateTrailingGarbage], which can be used to determine
590 /// `trailingGarbage` here.
591 @protected
592 void collectGarbage(int leadingGarbage, int trailingGarbage) {
593 assert(_debugAssertChildListLocked());
594 assert(childCount >= leadingGarbage + trailingGarbage);
595 invokeLayoutCallback<SliverConstraints>((SliverConstraints constraints) {
596 while (leadingGarbage > 0) {
597 _destroyOrCacheChild(firstChild!);
598 leadingGarbage -= 1;
599 }
600 while (trailingGarbage > 0) {
601 _destroyOrCacheChild(lastChild!);
602 trailingGarbage -= 1;
603 }
604 // Ask the child manager to remove the children that are no longer being
605 // kept alive. (This should cause _keepAliveBucket to change, so we have
606 // to prepare our list ahead of time.)
607 _keepAliveBucket.values
608 .where((RenderBox child) {
609 final SliverMultiBoxAdaptorParentData childParentData =
610 child.parentData! as SliverMultiBoxAdaptorParentData;
611 return !childParentData.keepAlive;
612 })
613 .toList()
614 .forEach(_childManager.removeChild);
615 assert(
616 _keepAliveBucket.values.where((RenderBox child) {
617 final SliverMultiBoxAdaptorParentData childParentData =
618 child.parentData! as SliverMultiBoxAdaptorParentData;
619 return !childParentData.keepAlive;
620 }).isEmpty,
621 );
622 });
623 }
624
625 /// Returns the index of the given child, as given by the
626 /// [SliverMultiBoxAdaptorParentData.index] field of the child's [parentData].
627 int indexOf(RenderBox child) {
628 final SliverMultiBoxAdaptorParentData childParentData =
629 child.parentData! as SliverMultiBoxAdaptorParentData;
630 assert(childParentData.index != null);
631 return childParentData.index!;
632 }
633
634 /// Returns the dimension of the given child in the main axis, as given by the
635 /// child's [RenderBox.size] property. This is only valid after layout.
636 @protected
637 double paintExtentOf(RenderBox child) {
638 assert(child.hasSize);
639 return switch (constraints.axis) {
640 Axis.horizontal => child.size.width,
641 Axis.vertical => child.size.height,
642 };
643 }
644
645 @override
646 bool hitTestChildren(
647 SliverHitTestResult result, {
648 required double mainAxisPosition,
649 required double crossAxisPosition,
650 }) {
651 RenderBox? child = lastChild;
652 final BoxHitTestResult boxResult = BoxHitTestResult.wrap(result);
653 while (child != null) {
654 if (hitTestBoxChild(
655 boxResult,
656 child,
657 mainAxisPosition: mainAxisPosition,
658 crossAxisPosition: crossAxisPosition,
659 )) {
660 return true;
661 }
662 child = childBefore(child);
663 }
664 return false;
665 }
666
667 @override
668 double childMainAxisPosition(RenderBox child) {
669 return childScrollOffset(child)! - constraints.scrollOffset;
670 }
671
672 @override
673 double? childScrollOffset(RenderObject child) {
674 assert(child.parent == this);
675 final SliverMultiBoxAdaptorParentData childParentData =
676 child.parentData! as SliverMultiBoxAdaptorParentData;
677 return childParentData.layoutOffset;
678 }
679
680 @override
681 bool paintsChild(RenderBox child) {
682 final SliverMultiBoxAdaptorParentData? childParentData =
683 child.parentData as SliverMultiBoxAdaptorParentData?;
684 return childParentData?.index != null && !_keepAliveBucket.containsKey(childParentData!.index);
685 }
686
687 @override
688 void applyPaintTransform(RenderBox child, Matrix4 transform) {
689 if (!paintsChild(child)) {
690 // This can happen if some child asks for the global transform even though
691 // they are not getting painted. In that case, the transform sets set to
692 // zero since [applyPaintTransformForBoxChild] would end up throwing due
693 // to the child not being configured correctly for applying a transform.
694 // There's no assert here because asking for the paint transform is a
695 // valid thing to do even if a child would not be painted, but there is no
696 // meaningful non-zero matrix to use in this case.
697 transform.setZero();
698 } else {
699 applyPaintTransformForBoxChild(child, transform);
700 }
701 }
702
703 @override
704 void paint(PaintingContext context, Offset offset) {
705 if (firstChild == null) {
706 return;
707 }
708 // offset is to the top-left corner, regardless of our axis direction.
709 // originOffset gives us the delta from the real origin to the origin in the axis direction.
710 final Offset mainAxisUnit, crossAxisUnit, originOffset;
711 final bool addExtent;
712 switch (applyGrowthDirectionToAxisDirection(
713 constraints.axisDirection,
714 constraints.growthDirection,
715 )) {
716 case AxisDirection.up:
717 mainAxisUnit = const Offset(0.0, -1.0);
718 crossAxisUnit = const Offset(1.0, 0.0);
719 originOffset = offset + Offset(0.0, geometry!.paintExtent);
720 addExtent = true;
721 case AxisDirection.right:
722 mainAxisUnit = const Offset(1.0, 0.0);
723 crossAxisUnit = const Offset(0.0, 1.0);
724 originOffset = offset;
725 addExtent = false;
726 case AxisDirection.down:
727 mainAxisUnit = const Offset(0.0, 1.0);
728 crossAxisUnit = const Offset(1.0, 0.0);
729 originOffset = offset;
730 addExtent = false;
731 case AxisDirection.left:
732 mainAxisUnit = const Offset(-1.0, 0.0);
733 crossAxisUnit = const Offset(0.0, 1.0);
734 originOffset = offset + Offset(geometry!.paintExtent, 0.0);
735 addExtent = true;
736 }
737 RenderBox? child = firstChild;
738 while (child != null) {
739 final double mainAxisDelta = childMainAxisPosition(child);
740 final double crossAxisDelta = childCrossAxisPosition(child);
741 Offset childOffset = Offset(
742 originOffset.dx + mainAxisUnit.dx * mainAxisDelta + crossAxisUnit.dx * crossAxisDelta,
743 originOffset.dy + mainAxisUnit.dy * mainAxisDelta + crossAxisUnit.dy * crossAxisDelta,
744 );
745 if (addExtent) {
746 childOffset += mainAxisUnit * paintExtentOf(child);
747 }
748
749 // If the child's visible interval (mainAxisDelta, mainAxisDelta + paintExtentOf(child))
750 // does not intersect the paint extent interval (0, constraints.remainingPaintExtent), it's hidden.
751 if (mainAxisDelta < constraints.remainingPaintExtent &&
752 mainAxisDelta + paintExtentOf(child) > 0) {
753 context.paintChild(child, childOffset);
754 }
755
756 child = childAfter(child);
757 }
758 }
759
760 @override
761 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
762 super.debugFillProperties(properties);
763 properties.add(
764 DiagnosticsNode.message(
765 firstChild != null
766 ? 'currently live children: ${indexOf(firstChild!)} to ${indexOf(lastChild!)}'
767 : 'no children current live',
768 ),
769 );
770 }
771
772 /// Asserts that the reified child list is not empty and has a contiguous
773 /// sequence of indices.
774 ///
775 /// Always returns true.
776 bool debugAssertChildListIsNonEmptyAndContiguous() {
777 assert(() {
778 assert(firstChild != null);
779 int index = indexOf(firstChild!);
780 RenderBox? child = childAfter(firstChild!);
781 while (child != null) {
782 index += 1;
783 assert(indexOf(child) == index);
784 child = childAfter(child);
785 }
786 return true;
787 }());
788 return true;
789 }
790
791 @override
792 List<DiagnosticsNode> debugDescribeChildren() {
793 final List<DiagnosticsNode> children = <DiagnosticsNode>[];
794 if (firstChild != null) {
795 RenderBox? child = firstChild;
796 while (true) {
797 final SliverMultiBoxAdaptorParentData childParentData =
798 child!.parentData! as SliverMultiBoxAdaptorParentData;
799 children.add(child.toDiagnosticsNode(name: 'child with index ${childParentData.index}'));
800 if (child == lastChild) {
801 break;
802 }
803 child = childParentData.nextSibling;
804 }
805 }
806 if (_keepAliveBucket.isNotEmpty) {
807 final List<int> indices = _keepAliveBucket.keys.toList()..sort();
808 for (final int index in indices) {
809 children.add(
810 _keepAliveBucket[index]!.toDiagnosticsNode(
811 name: 'child with index $index (kept alive but not laid out)',
812 style: DiagnosticsTreeStyle.offstage,
813 ),
814 );
815 }
816 }
817 return children;
818 }
819}
820

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com