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