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 | import 'package:flutter/foundation.dart'; |
6 | import 'package:flutter/rendering.dart'; |
7 | |
8 | import 'automatic_keep_alive.dart'; |
9 | import 'basic.dart'; |
10 | import 'framework.dart'; |
11 | import 'selection_container.dart'; |
12 | import 'two_dimensional_viewport.dart'; |
13 | |
14 | export 'package:flutter/rendering.dart' show |
15 | SliverGridDelegate, |
16 | SliverGridDelegateWithFixedCrossAxisCount, |
17 | SliverGridDelegateWithMaxCrossAxisExtent; |
18 | |
19 | // Examples can assume: |
20 | // late SliverGridDelegateWithMaxCrossAxisExtent _gridDelegate; |
21 | // abstract class SomeWidget extends StatefulWidget { const SomeWidget({super.key}); } |
22 | // typedef ChildWidget = Placeholder; |
23 | |
24 | /// A callback which produces a semantic index given a widget and the local index. |
25 | /// |
26 | /// Return a null value to prevent a widget from receiving an index. |
27 | /// |
28 | /// A semantic index is used to tag child semantic nodes for accessibility |
29 | /// announcements in scroll view. |
30 | /// |
31 | /// See also: |
32 | /// |
33 | /// * [CustomScrollView], for an explanation of scroll semantics. |
34 | /// * [SliverChildBuilderDelegate], for an explanation of how this is used to |
35 | /// generate indexes. |
36 | typedef SemanticIndexCallback = int? Function(Widget widget, int localIndex); |
37 | |
38 | int _kDefaultSemanticIndexCallback(Widget _, int localIndex) => localIndex; |
39 | |
40 | /// A delegate that supplies children for slivers. |
41 | /// |
42 | /// Many slivers lazily construct their box children to avoid creating more |
43 | /// children than are visible through the [Viewport]. Rather than receiving |
44 | /// their children as an explicit [List], they receive their children using a |
45 | /// [SliverChildDelegate]. |
46 | /// |
47 | /// It's uncommon to subclass [SliverChildDelegate]. Instead, consider using one |
48 | /// of the existing subclasses that provide adaptors to builder callbacks or |
49 | /// explicit child lists. |
50 | /// |
51 | /// {@template flutter.widgets.SliverChildDelegate.lifecycle} |
52 | /// ## Child elements' lifecycle |
53 | /// |
54 | /// ### Creation |
55 | /// |
56 | /// While laying out the list, visible children's elements, states and render |
57 | /// objects will be created lazily based on existing widgets (such as in the |
58 | /// case of [SliverChildListDelegate]) or lazily provided ones (such as in the |
59 | /// case of [SliverChildBuilderDelegate]). |
60 | /// |
61 | /// ### Destruction |
62 | /// |
63 | /// When a child is scrolled out of view, the associated element subtree, states |
64 | /// and render objects are destroyed. A new child at the same position in the |
65 | /// sliver will be lazily recreated along with new elements, states and render |
66 | /// objects when it is scrolled back. |
67 | /// |
68 | /// ### Destruction mitigation |
69 | /// |
70 | /// In order to preserve state as child elements are scrolled in and out of |
71 | /// view, the following options are possible: |
72 | /// |
73 | /// * Moving the ownership of non-trivial UI-state-driving business logic |
74 | /// out of the sliver child subtree. For instance, if a list contains posts |
75 | /// with their number of upvotes coming from a cached network response, store |
76 | /// the list of posts and upvote number in a data model outside the list. Let |
77 | /// the sliver child UI subtree be easily recreate-able from the |
78 | /// source-of-truth model object. Use [StatefulWidget]s in the child widget |
79 | /// subtree to store instantaneous UI state only. |
80 | /// |
81 | /// * Letting [KeepAlive] be the root widget of the sliver child widget subtree |
82 | /// that needs to be preserved. The [KeepAlive] widget marks the child |
83 | /// subtree's top render object child for keepalive. When the associated top |
84 | /// render object is scrolled out of view, the sliver keeps the child's |
85 | /// render object (and by extension, its associated elements and states) in a |
86 | /// cache list instead of destroying them. When scrolled back into view, the |
87 | /// render object is repainted as-is (if it wasn't marked dirty in the |
88 | /// interim). |
89 | /// |
90 | /// This only works if the [SliverChildDelegate] subclasses don't wrap the |
91 | /// child widget subtree with other widgets such as [AutomaticKeepAlive] and |
92 | /// [RepaintBoundary] via `addAutomaticKeepAlives` and |
93 | /// `addRepaintBoundaries`. |
94 | /// |
95 | /// * Using [AutomaticKeepAlive] widgets (inserted by default in |
96 | /// [SliverChildListDelegate] or [SliverChildListDelegate]). |
97 | /// [AutomaticKeepAlive] allows descendant widgets to control whether the |
98 | /// subtree is actually kept alive or not. This behavior is in contrast with |
99 | /// [KeepAlive], which will unconditionally keep the subtree alive. |
100 | /// |
101 | /// As an example, the [EditableText] widget signals its sliver child element |
102 | /// subtree to stay alive while its text field has input focus. If it doesn't |
103 | /// have focus and no other descendants signaled for keepalive via a |
104 | /// [KeepAliveNotification], the sliver child element subtree will be |
105 | /// destroyed when scrolled away. |
106 | /// |
107 | /// [AutomaticKeepAlive] descendants typically signal it to be kept alive by |
108 | /// using the [AutomaticKeepAliveClientMixin], then implementing the |
109 | /// [AutomaticKeepAliveClientMixin.wantKeepAlive] getter and calling |
110 | /// [AutomaticKeepAliveClientMixin.updateKeepAlive]. |
111 | /// |
112 | /// ## Using more than one delegate in a [Viewport] |
113 | /// |
114 | /// If multiple delegates are used in a single scroll view, the first child of |
115 | /// each delegate will always be laid out, even if it extends beyond the |
116 | /// currently viewable area. This is because at least one child is required in |
117 | /// order to [estimateMaxScrollOffset] for the whole scroll view, as it uses the |
118 | /// currently built children to estimate the remaining children's extent. |
119 | /// {@endtemplate} |
120 | /// |
121 | /// See also: |
122 | /// |
123 | /// * [SliverChildBuilderDelegate], which is a delegate that uses a builder |
124 | /// callback to construct the children. |
125 | /// * [SliverChildListDelegate], which is a delegate that has an explicit list |
126 | /// of children. |
127 | abstract class SliverChildDelegate { |
128 | /// Abstract const constructor. This constructor enables subclasses to provide |
129 | /// const constructors so that they can be used in const expressions. |
130 | const SliverChildDelegate(); |
131 | |
132 | /// Returns the child with the given index. |
133 | /// |
134 | /// Should return null if asked to build a widget with a greater |
135 | /// index than exists. If this returns null, [estimatedChildCount] |
136 | /// must subsequently return a precise non-null value (which is then |
137 | /// used to implement [RenderSliverBoxChildManager.childCount]). |
138 | /// |
139 | /// Subclasses typically override this function and wrap their children in |
140 | /// [AutomaticKeepAlive], [IndexedSemantics], and [RepaintBoundary] widgets. |
141 | /// |
142 | /// The values returned by this method are cached. To indicate that the |
143 | /// widgets have changed, a new delegate must be provided, and the new |
144 | /// delegate's [shouldRebuild] method must return true. |
145 | Widget? build(BuildContext context, int index); |
146 | |
147 | /// Returns an estimate of the number of children this delegate will build. |
148 | /// |
149 | /// Used to estimate the maximum scroll offset if [estimateMaxScrollOffset] |
150 | /// returns null. |
151 | /// |
152 | /// Return null if there are an unbounded number of children or if it would |
153 | /// be too difficult to estimate the number of children. |
154 | /// |
155 | /// This must return a precise number once [build] has returned null, as it |
156 | /// used to implement [RenderSliverBoxChildManager.childCount]. |
157 | int? get estimatedChildCount => null; |
158 | |
159 | /// Returns an estimate of the max scroll extent for all the children. |
160 | /// |
161 | /// Subclasses should override this function if they have additional |
162 | /// information about their max scroll extent. |
163 | /// |
164 | /// The default implementation returns null, which causes the caller to |
165 | /// extrapolate the max scroll offset from the given parameters. |
166 | double? estimateMaxScrollOffset( |
167 | int firstIndex, |
168 | int lastIndex, |
169 | double leadingScrollOffset, |
170 | double trailingScrollOffset, |
171 | ) => null; |
172 | |
173 | /// Called at the end of layout to indicate that layout is now complete. |
174 | /// |
175 | /// The `firstIndex` argument is the index of the first child that was |
176 | /// included in the current layout. The `lastIndex` argument is the index of |
177 | /// the last child that was included in the current layout. |
178 | /// |
179 | /// Useful for subclasses that which to track which children are included in |
180 | /// the underlying render tree. |
181 | void didFinishLayout(int firstIndex, int lastIndex) { } |
182 | |
183 | /// Called whenever a new instance of the child delegate class is |
184 | /// provided to the sliver. |
185 | /// |
186 | /// If the new instance represents different information than the old |
187 | /// instance, then the method should return true, otherwise it should return |
188 | /// false. |
189 | /// |
190 | /// If the method returns false, then the [build] call might be optimized |
191 | /// away. |
192 | bool shouldRebuild(covariant SliverChildDelegate oldDelegate); |
193 | |
194 | /// Find index of child element with associated key. |
195 | /// |
196 | /// This will be called during `performRebuild` in [SliverMultiBoxAdaptorElement] |
197 | /// to check if a child has moved to a different position. It should return the |
198 | /// index of the child element with associated key, null if not found. |
199 | /// |
200 | /// If not provided, a child widget may not map to its existing [RenderObject] |
201 | /// when the order of children returned from the children builder changes. |
202 | /// This may result in state-loss. |
203 | int? findIndexByKey(Key key) => null; |
204 | |
205 | @override |
206 | String toString() { |
207 | final List<String> description = <String>[]; |
208 | debugFillDescription(description); |
209 | return ' ${describeIdentity(this)}( ${description.join(", " )})' ; |
210 | } |
211 | |
212 | /// Add additional information to the given description for use by [toString]. |
213 | @protected |
214 | @mustCallSuper |
215 | void debugFillDescription(List<String> description) { |
216 | try { |
217 | final int? children = estimatedChildCount; |
218 | if (children != null) { |
219 | description.add('estimated child count: $children' ); |
220 | } |
221 | } catch (e) { |
222 | // The exception is forwarded to widget inspector. |
223 | description.add('estimated child count: EXCEPTION ( ${e.runtimeType})' ); |
224 | } |
225 | } |
226 | } |
227 | |
228 | class _SaltedValueKey extends ValueKey<Key> { |
229 | const _SaltedValueKey(super.value); |
230 | } |
231 | |
232 | /// Called to find the new index of a child based on its `key` in case of |
233 | /// reordering. |
234 | /// |
235 | /// If the child with the `key` is no longer present, null is returned. |
236 | /// |
237 | /// Used by [SliverChildBuilderDelegate.findChildIndexCallback]. |
238 | typedef ChildIndexGetter = int? Function(Key key); |
239 | |
240 | /// A delegate that supplies children for slivers using a builder callback. |
241 | /// |
242 | /// Many slivers lazily construct their box children to avoid creating more |
243 | /// children than are visible through the [Viewport]. This delegate provides |
244 | /// children using a [NullableIndexedWidgetBuilder] callback, so that the children do |
245 | /// not even have to be built until they are displayed. |
246 | /// |
247 | /// The widgets returned from the builder callback are automatically wrapped in |
248 | /// [AutomaticKeepAlive] widgets if [addAutomaticKeepAlives] is true (the |
249 | /// default) and in [RepaintBoundary] widgets if [addRepaintBoundaries] is true |
250 | /// (also the default). |
251 | /// |
252 | /// ## Accessibility |
253 | /// |
254 | /// The [CustomScrollView] requires that its semantic children are annotated |
255 | /// using [IndexedSemantics]. This is done by default in the delegate with |
256 | /// the `addSemanticIndexes` parameter set to true. |
257 | /// |
258 | /// If multiple delegates are used in a single scroll view, then the indexes |
259 | /// will not be correct by default. The `semanticIndexOffset` can be used to |
260 | /// offset the semantic indexes of each delegate so that the indexes are |
261 | /// monotonically increasing. For example, if a scroll view contains two |
262 | /// delegates where the first has 10 children contributing semantics, then the |
263 | /// second delegate should offset its children by 10. |
264 | /// |
265 | /// {@tool snippet} |
266 | /// |
267 | /// This sample code shows how to use `semanticIndexOffset` to handle multiple |
268 | /// delegates in a single scroll view. |
269 | /// |
270 | /// ```dart |
271 | /// CustomScrollView( |
272 | /// semanticChildCount: 4, |
273 | /// slivers: <Widget>[ |
274 | /// SliverGrid( |
275 | /// gridDelegate: _gridDelegate, |
276 | /// delegate: SliverChildBuilderDelegate( |
277 | /// (BuildContext context, int index) { |
278 | /// return const Text('...'); |
279 | /// }, |
280 | /// childCount: 2, |
281 | /// ), |
282 | /// ), |
283 | /// SliverGrid( |
284 | /// gridDelegate: _gridDelegate, |
285 | /// delegate: SliverChildBuilderDelegate( |
286 | /// (BuildContext context, int index) { |
287 | /// return const Text('...'); |
288 | /// }, |
289 | /// childCount: 2, |
290 | /// semanticIndexOffset: 2, |
291 | /// ), |
292 | /// ), |
293 | /// ], |
294 | /// ) |
295 | /// ``` |
296 | /// {@end-tool} |
297 | /// |
298 | /// In certain cases, only a subset of child widgets should be annotated |
299 | /// with a semantic index. For example, in [ListView.separated()] the |
300 | /// separators do not have an index associated with them. This is done by |
301 | /// providing a `semanticIndexCallback` which returns null for separators |
302 | /// indexes and rounds the non-separator indexes down by half. |
303 | /// |
304 | /// {@tool snippet} |
305 | /// |
306 | /// This sample code shows how to use `semanticIndexCallback` to handle |
307 | /// annotating a subset of child nodes with a semantic index. There is |
308 | /// a [Spacer] widget at odd indexes which should not have a semantic |
309 | /// index. |
310 | /// |
311 | /// ```dart |
312 | /// CustomScrollView( |
313 | /// semanticChildCount: 5, |
314 | /// slivers: <Widget>[ |
315 | /// SliverGrid( |
316 | /// gridDelegate: _gridDelegate, |
317 | /// delegate: SliverChildBuilderDelegate( |
318 | /// (BuildContext context, int index) { |
319 | /// if (index.isEven) { |
320 | /// return const Text('...'); |
321 | /// } |
322 | /// return const Spacer(); |
323 | /// }, |
324 | /// semanticIndexCallback: (Widget widget, int localIndex) { |
325 | /// if (localIndex.isEven) { |
326 | /// return localIndex ~/ 2; |
327 | /// } |
328 | /// return null; |
329 | /// }, |
330 | /// childCount: 10, |
331 | /// ), |
332 | /// ), |
333 | /// ], |
334 | /// ) |
335 | /// ``` |
336 | /// {@end-tool} |
337 | /// |
338 | /// See also: |
339 | /// |
340 | /// * [SliverChildListDelegate], which is a delegate that has an explicit list |
341 | /// of children. |
342 | /// * [IndexedSemantics], for an example of manually annotating child nodes |
343 | /// with semantic indexes. |
344 | class SliverChildBuilderDelegate extends SliverChildDelegate { |
345 | /// Creates a delegate that supplies children for slivers using the given |
346 | /// builder callback. |
347 | /// |
348 | /// The [builder], [addAutomaticKeepAlives], [addRepaintBoundaries], |
349 | /// [addSemanticIndexes], and [semanticIndexCallback] arguments must not be |
350 | /// null. |
351 | /// |
352 | /// If the order in which [builder] returns children ever changes, consider |
353 | /// providing a [findChildIndexCallback]. This allows the delegate to find the |
354 | /// new index for a child that was previously located at a different index to |
355 | /// attach the existing state to the [Widget] at its new location. |
356 | const SliverChildBuilderDelegate( |
357 | this.builder, { |
358 | this.findChildIndexCallback, |
359 | this.childCount, |
360 | this.addAutomaticKeepAlives = true, |
361 | this.addRepaintBoundaries = true, |
362 | this.addSemanticIndexes = true, |
363 | this.semanticIndexCallback = _kDefaultSemanticIndexCallback, |
364 | this.semanticIndexOffset = 0, |
365 | }); |
366 | |
367 | /// Called to build children for the sliver. |
368 | /// |
369 | /// Will be called only for indices greater than or equal to zero and less |
370 | /// than [childCount] (if [childCount] is non-null). |
371 | /// |
372 | /// Should return null if asked to build a widget with a greater index than |
373 | /// exists. |
374 | /// |
375 | /// May result in an infinite loop or run out of memory if [childCount] is null |
376 | /// and the [builder] always provides a zero-size widget (such as `Container()` |
377 | /// or `SizedBox.shrink()`). If possible, provide children with non-zero size, |
378 | /// return null from [builder], or set a [childCount]. |
379 | /// |
380 | /// The delegate wraps the children returned by this builder in |
381 | /// [RepaintBoundary] widgets. |
382 | final NullableIndexedWidgetBuilder builder; |
383 | |
384 | /// The total number of children this delegate can provide. |
385 | /// |
386 | /// If null, the number of children is determined by the least index for which |
387 | /// [builder] returns null. |
388 | /// |
389 | /// May result in an infinite loop or run out of memory if [childCount] is null |
390 | /// and the [builder] always provides a zero-size widget (such as `Container()` |
391 | /// or `SizedBox.shrink()`). If possible, provide children with non-zero size, |
392 | /// return null from [builder], or set a [childCount]. |
393 | final int? childCount; |
394 | |
395 | /// {@template flutter.widgets.SliverChildBuilderDelegate.addAutomaticKeepAlives} |
396 | /// Whether to wrap each child in an [AutomaticKeepAlive]. |
397 | /// |
398 | /// Typically, lazily laid out children are wrapped in [AutomaticKeepAlive] |
399 | /// widgets so that the children can use [KeepAliveNotification]s to preserve |
400 | /// their state when they would otherwise be garbage collected off-screen. |
401 | /// |
402 | /// This feature (and [addRepaintBoundaries]) must be disabled if the children |
403 | /// are going to manually maintain their [KeepAlive] state. It may also be |
404 | /// more efficient to disable this feature if it is known ahead of time that |
405 | /// none of the children will ever try to keep themselves alive. |
406 | /// |
407 | /// Defaults to true. |
408 | /// {@endtemplate} |
409 | final bool addAutomaticKeepAlives; |
410 | |
411 | /// {@template flutter.widgets.SliverChildBuilderDelegate.addRepaintBoundaries} |
412 | /// Whether to wrap each child in a [RepaintBoundary]. |
413 | /// |
414 | /// Typically, children in a scrolling container are wrapped in repaint |
415 | /// boundaries so that they do not need to be repainted as the list scrolls. |
416 | /// If the children are easy to repaint (e.g., solid color blocks or a short |
417 | /// snippet of text), it might be more efficient to not add a repaint boundary |
418 | /// and instead always repaint the children during scrolling. |
419 | /// |
420 | /// Defaults to true. |
421 | /// {@endtemplate} |
422 | final bool addRepaintBoundaries; |
423 | |
424 | /// {@template flutter.widgets.SliverChildBuilderDelegate.addSemanticIndexes} |
425 | /// Whether to wrap each child in an [IndexedSemantics]. |
426 | /// |
427 | /// Typically, children in a scrolling container must be annotated with a |
428 | /// semantic index in order to generate the correct accessibility |
429 | /// announcements. This should only be set to false if the indexes have |
430 | /// already been provided by an [IndexedSemantics] widget. |
431 | /// |
432 | /// Defaults to true. |
433 | /// |
434 | /// See also: |
435 | /// |
436 | /// * [IndexedSemantics], for an explanation of how to manually |
437 | /// provide semantic indexes. |
438 | /// {@endtemplate} |
439 | final bool addSemanticIndexes; |
440 | |
441 | /// {@template flutter.widgets.SliverChildBuilderDelegate.semanticIndexOffset} |
442 | /// An initial offset to add to the semantic indexes generated by this widget. |
443 | /// |
444 | /// Defaults to zero. |
445 | /// {@endtemplate} |
446 | final int semanticIndexOffset; |
447 | |
448 | /// {@template flutter.widgets.SliverChildBuilderDelegate.semanticIndexCallback} |
449 | /// A [SemanticIndexCallback] which is used when [addSemanticIndexes] is true. |
450 | /// |
451 | /// Defaults to providing an index for each widget. |
452 | /// {@endtemplate} |
453 | final SemanticIndexCallback semanticIndexCallback; |
454 | |
455 | /// {@template flutter.widgets.SliverChildBuilderDelegate.findChildIndexCallback} |
456 | /// Called to find the new index of a child based on its key in case of reordering. |
457 | /// |
458 | /// If not provided, a child widget may not map to its existing [RenderObject] |
459 | /// when the order of children returned from the children builder changes. |
460 | /// This may result in state-loss. |
461 | /// |
462 | /// This callback should take an input [Key], and it should return the |
463 | /// index of the child element with that associated key, or null if not found. |
464 | /// {@endtemplate} |
465 | final ChildIndexGetter? findChildIndexCallback; |
466 | |
467 | @override |
468 | int? findIndexByKey(Key key) { |
469 | if (findChildIndexCallback == null) { |
470 | return null; |
471 | } |
472 | final Key childKey; |
473 | if (key is _SaltedValueKey) { |
474 | final _SaltedValueKey saltedValueKey = key; |
475 | childKey = saltedValueKey.value; |
476 | } else { |
477 | childKey = key; |
478 | } |
479 | return findChildIndexCallback!(childKey); |
480 | } |
481 | |
482 | @override |
483 | @pragma('vm:notify-debugger-on-exception' ) |
484 | Widget? build(BuildContext context, int index) { |
485 | if (index < 0 || (childCount != null && index >= childCount!)) { |
486 | return null; |
487 | } |
488 | Widget? child; |
489 | try { |
490 | child = builder(context, index); |
491 | } catch (exception, stackTrace) { |
492 | child = _createErrorWidget(exception, stackTrace); |
493 | } |
494 | if (child == null) { |
495 | return null; |
496 | } |
497 | final Key? key = child.key != null ? _SaltedValueKey(child.key!) : null; |
498 | if (addRepaintBoundaries) { |
499 | child = RepaintBoundary(child: child); |
500 | } |
501 | if (addSemanticIndexes) { |
502 | final int? semanticIndex = semanticIndexCallback(child, index); |
503 | if (semanticIndex != null) { |
504 | child = IndexedSemantics(index: semanticIndex + semanticIndexOffset, child: child); |
505 | } |
506 | } |
507 | if (addAutomaticKeepAlives) { |
508 | child = AutomaticKeepAlive(child: _SelectionKeepAlive(child: child)); |
509 | } |
510 | return KeyedSubtree(key: key, child: child); |
511 | } |
512 | |
513 | @override |
514 | int? get estimatedChildCount => childCount; |
515 | |
516 | @override |
517 | bool shouldRebuild(covariant SliverChildBuilderDelegate oldDelegate) => true; |
518 | } |
519 | |
520 | /// A delegate that supplies children for slivers using an explicit list. |
521 | /// |
522 | /// Many slivers lazily construct their box children to avoid creating more |
523 | /// children than are visible through the [Viewport]. This delegate provides |
524 | /// children using an explicit list, which is convenient but reduces the benefit |
525 | /// of building children lazily. |
526 | /// |
527 | /// In general building all the widgets in advance is not efficient. It is |
528 | /// better to create a delegate that builds them on demand using |
529 | /// [SliverChildBuilderDelegate] or by subclassing [SliverChildDelegate] |
530 | /// directly. |
531 | /// |
532 | /// This class is provided for the cases where either the list of children is |
533 | /// known well in advance (ideally the children are themselves compile-time |
534 | /// constants, for example), and therefore will not be built each time the |
535 | /// delegate itself is created, or the list is small, such that it's likely |
536 | /// always visible (and thus there is nothing to be gained by building it on |
537 | /// demand). For example, the body of a dialog box might fit both of these |
538 | /// conditions. |
539 | /// |
540 | /// The widgets in the given [children] list are automatically wrapped in |
541 | /// [AutomaticKeepAlive] widgets if [addAutomaticKeepAlives] is true (the |
542 | /// default) and in [RepaintBoundary] widgets if [addRepaintBoundaries] is true |
543 | /// (also the default). |
544 | /// |
545 | /// ## Accessibility |
546 | /// |
547 | /// The [CustomScrollView] requires that its semantic children are annotated |
548 | /// using [IndexedSemantics]. This is done by default in the delegate with |
549 | /// the `addSemanticIndexes` parameter set to true. |
550 | /// |
551 | /// If multiple delegates are used in a single scroll view, then the indexes |
552 | /// will not be correct by default. The `semanticIndexOffset` can be used to |
553 | /// offset the semantic indexes of each delegate so that the indexes are |
554 | /// monotonically increasing. For example, if a scroll view contains two |
555 | /// delegates where the first has 10 children contributing semantics, then the |
556 | /// second delegate should offset its children by 10. |
557 | /// |
558 | /// In certain cases, only a subset of child widgets should be annotated |
559 | /// with a semantic index. For example, in [ListView.separated()] the |
560 | /// separators do not have an index associated with them. This is done by |
561 | /// providing a `semanticIndexCallback` which returns null for separators |
562 | /// indexes and rounds the non-separator indexes down by half. |
563 | /// |
564 | /// See [SliverChildBuilderDelegate] for sample code using |
565 | /// `semanticIndexOffset` and `semanticIndexCallback`. |
566 | /// |
567 | /// See also: |
568 | /// |
569 | /// * [SliverChildBuilderDelegate], which is a delegate that uses a builder |
570 | /// callback to construct the children. |
571 | class SliverChildListDelegate extends SliverChildDelegate { |
572 | /// Creates a delegate that supplies children for slivers using the given |
573 | /// list. |
574 | /// |
575 | /// The [children], [addAutomaticKeepAlives], [addRepaintBoundaries], |
576 | /// [addSemanticIndexes], and [semanticIndexCallback] arguments must not be |
577 | /// null. |
578 | /// |
579 | /// If the order of children never changes, consider using the constant |
580 | /// [SliverChildListDelegate.fixed] constructor. |
581 | SliverChildListDelegate( |
582 | this.children, { |
583 | this.addAutomaticKeepAlives = true, |
584 | this.addRepaintBoundaries = true, |
585 | this.addSemanticIndexes = true, |
586 | this.semanticIndexCallback = _kDefaultSemanticIndexCallback, |
587 | this.semanticIndexOffset = 0, |
588 | }) : _keyToIndex = <Key?, int>{null: 0}; |
589 | |
590 | /// Creates a constant version of the delegate that supplies children for |
591 | /// slivers using the given list. |
592 | /// |
593 | /// If the order of the children will change, consider using the regular |
594 | /// [SliverChildListDelegate] constructor. |
595 | /// |
596 | /// The [children], [addAutomaticKeepAlives], [addRepaintBoundaries], |
597 | /// [addSemanticIndexes], and [semanticIndexCallback] arguments must not be |
598 | /// null. |
599 | const SliverChildListDelegate.fixed( |
600 | this.children, { |
601 | this.addAutomaticKeepAlives = true, |
602 | this.addRepaintBoundaries = true, |
603 | this.addSemanticIndexes = true, |
604 | this.semanticIndexCallback = _kDefaultSemanticIndexCallback, |
605 | this.semanticIndexOffset = 0, |
606 | }) : _keyToIndex = null; |
607 | |
608 | /// {@macro flutter.widgets.SliverChildBuilderDelegate.addAutomaticKeepAlives} |
609 | final bool addAutomaticKeepAlives; |
610 | |
611 | /// {@macro flutter.widgets.SliverChildBuilderDelegate.addRepaintBoundaries} |
612 | final bool addRepaintBoundaries; |
613 | |
614 | /// {@macro flutter.widgets.SliverChildBuilderDelegate.addSemanticIndexes} |
615 | final bool addSemanticIndexes; |
616 | |
617 | /// {@macro flutter.widgets.SliverChildBuilderDelegate.semanticIndexOffset} |
618 | final int semanticIndexOffset; |
619 | |
620 | /// {@macro flutter.widgets.SliverChildBuilderDelegate.semanticIndexCallback} |
621 | final SemanticIndexCallback semanticIndexCallback; |
622 | |
623 | /// The widgets to display. |
624 | /// |
625 | /// If this list is going to be mutated, it is usually wise to put a [Key] on |
626 | /// each of the child widgets, so that the framework can match old |
627 | /// configurations to new configurations and maintain the underlying render |
628 | /// objects. |
629 | /// |
630 | /// Also, a [Widget] in Flutter is immutable, so directly modifying the |
631 | /// [children] such as `someWidget.children.add(...)` or |
632 | /// passing a reference of the original list value to the [children] parameter |
633 | /// will result in incorrect behaviors. Whenever the |
634 | /// children list is modified, a new list object must be provided. |
635 | /// |
636 | /// The following code corrects the problem mentioned above. |
637 | /// |
638 | /// ```dart |
639 | /// class SomeWidgetState extends State<SomeWidget> { |
640 | /// final List<Widget> _children = <Widget>[]; |
641 | /// |
642 | /// void someHandler() { |
643 | /// setState(() { |
644 | /// // The key here allows Flutter to reuse the underlying render |
645 | /// // objects even if the children list is recreated. |
646 | /// _children.add(ChildWidget(key: UniqueKey())); |
647 | /// }); |
648 | /// } |
649 | /// |
650 | /// @override |
651 | /// Widget build(BuildContext context) { |
652 | /// // Always create a new list of children as a Widget is immutable. |
653 | /// return PageView(children: List<Widget>.of(_children)); |
654 | /// } |
655 | /// } |
656 | /// ``` |
657 | final List<Widget> children; |
658 | |
659 | /// A map to cache key to index lookup for children. |
660 | /// |
661 | /// _keyToIndex[null] is used as current index during the lazy loading process |
662 | /// in [_findChildIndex]. _keyToIndex should never be used for looking up null key. |
663 | final Map<Key?, int>? _keyToIndex; |
664 | |
665 | bool get _isConstantInstance => _keyToIndex == null; |
666 | |
667 | int? _findChildIndex(Key key) { |
668 | if (_isConstantInstance) { |
669 | return null; |
670 | } |
671 | // Lazily fill the [_keyToIndex]. |
672 | if (!_keyToIndex!.containsKey(key)) { |
673 | int index = _keyToIndex[null]!; |
674 | while (index < children.length) { |
675 | final Widget child = children[index]; |
676 | if (child.key != null) { |
677 | _keyToIndex[child.key] = index; |
678 | } |
679 | if (child.key == key) { |
680 | // Record current index for next function call. |
681 | _keyToIndex[null] = index + 1; |
682 | return index; |
683 | } |
684 | index += 1; |
685 | } |
686 | _keyToIndex[null] = index; |
687 | } else { |
688 | return _keyToIndex[key]; |
689 | } |
690 | return null; |
691 | } |
692 | |
693 | @override |
694 | int? findIndexByKey(Key key) { |
695 | final Key childKey; |
696 | if (key is _SaltedValueKey) { |
697 | final _SaltedValueKey saltedValueKey = key; |
698 | childKey = saltedValueKey.value; |
699 | } else { |
700 | childKey = key; |
701 | } |
702 | return _findChildIndex(childKey); |
703 | } |
704 | |
705 | @override |
706 | Widget? build(BuildContext context, int index) { |
707 | if (index < 0 || index >= children.length) { |
708 | return null; |
709 | } |
710 | Widget child = children[index]; |
711 | final Key? key = child.key != null? _SaltedValueKey(child.key!) : null; |
712 | if (addRepaintBoundaries) { |
713 | child = RepaintBoundary(child: child); |
714 | } |
715 | if (addSemanticIndexes) { |
716 | final int? semanticIndex = semanticIndexCallback(child, index); |
717 | if (semanticIndex != null) { |
718 | child = IndexedSemantics(index: semanticIndex + semanticIndexOffset, child: child); |
719 | } |
720 | } |
721 | if (addAutomaticKeepAlives) { |
722 | child = AutomaticKeepAlive(child: _SelectionKeepAlive(child: child)); |
723 | } |
724 | |
725 | return KeyedSubtree(key: key, child: child); |
726 | } |
727 | |
728 | @override |
729 | int? get estimatedChildCount => children.length; |
730 | |
731 | @override |
732 | bool shouldRebuild(covariant SliverChildListDelegate oldDelegate) { |
733 | return children != oldDelegate.children; |
734 | } |
735 | } |
736 | |
737 | class _SelectionKeepAlive extends StatefulWidget { |
738 | /// Creates a widget that listens to [KeepAliveNotification]s and maintains a |
739 | /// [KeepAlive] widget appropriately. |
740 | const _SelectionKeepAlive({ |
741 | required this.child, |
742 | }); |
743 | |
744 | /// The widget below this widget in the tree. |
745 | /// |
746 | /// {@macro flutter.widgets.ProxyWidget.child} |
747 | final Widget child; |
748 | |
749 | @override |
750 | State<_SelectionKeepAlive> createState() => _SelectionKeepAliveState(); |
751 | } |
752 | |
753 | class _SelectionKeepAliveState extends State<_SelectionKeepAlive> with AutomaticKeepAliveClientMixin implements SelectionRegistrar { |
754 | Set<Selectable>? _selectablesWithSelections; |
755 | Map<Selectable, VoidCallback>? _selectableAttachments; |
756 | SelectionRegistrar? _registrar; |
757 | |
758 | @override |
759 | bool get wantKeepAlive => _wantKeepAlive; |
760 | bool _wantKeepAlive = false; |
761 | set wantKeepAlive(bool value) { |
762 | if (_wantKeepAlive != value) { |
763 | _wantKeepAlive = value; |
764 | updateKeepAlive(); |
765 | } |
766 | } |
767 | |
768 | VoidCallback listensTo(Selectable selectable) { |
769 | return () { |
770 | if (selectable.value.hasSelection) { |
771 | _updateSelectablesWithSelections(selectable, add: true); |
772 | } else { |
773 | _updateSelectablesWithSelections(selectable, add: false); |
774 | } |
775 | }; |
776 | } |
777 | |
778 | void _updateSelectablesWithSelections(Selectable selectable, {required bool add}) { |
779 | if (add) { |
780 | assert(selectable.value.hasSelection); |
781 | _selectablesWithSelections ??= <Selectable>{}; |
782 | _selectablesWithSelections!.add(selectable); |
783 | } else { |
784 | _selectablesWithSelections?.remove(selectable); |
785 | } |
786 | wantKeepAlive = _selectablesWithSelections?.isNotEmpty ?? false; |
787 | } |
788 | |
789 | @override |
790 | void didChangeDependencies() { |
791 | super.didChangeDependencies(); |
792 | final SelectionRegistrar? newRegistrar = SelectionContainer.maybeOf(context); |
793 | if (_registrar != newRegistrar) { |
794 | if (_registrar != null) { |
795 | _selectableAttachments?.keys.forEach(_registrar!.remove); |
796 | } |
797 | _registrar = newRegistrar; |
798 | if (_registrar != null) { |
799 | _selectableAttachments?.keys.forEach(_registrar!.add); |
800 | } |
801 | } |
802 | } |
803 | |
804 | @override |
805 | void add(Selectable selectable) { |
806 | final VoidCallback attachment = listensTo(selectable); |
807 | selectable.addListener(attachment); |
808 | _selectableAttachments ??= <Selectable, VoidCallback>{}; |
809 | _selectableAttachments![selectable] = attachment; |
810 | _registrar!.add(selectable); |
811 | if (selectable.value.hasSelection) { |
812 | _updateSelectablesWithSelections(selectable, add: true); |
813 | } |
814 | } |
815 | |
816 | @override |
817 | void remove(Selectable selectable) { |
818 | if (_selectableAttachments == null) { |
819 | return; |
820 | } |
821 | assert(_selectableAttachments!.containsKey(selectable)); |
822 | final VoidCallback attachment = _selectableAttachments!.remove(selectable)!; |
823 | selectable.removeListener(attachment); |
824 | _registrar!.remove(selectable); |
825 | _updateSelectablesWithSelections(selectable, add: false); |
826 | } |
827 | |
828 | @override |
829 | void dispose() { |
830 | if (_selectableAttachments != null) { |
831 | for (final Selectable selectable in _selectableAttachments!.keys) { |
832 | _registrar!.remove(selectable); |
833 | selectable.removeListener(_selectableAttachments![selectable]!); |
834 | } |
835 | _selectableAttachments = null; |
836 | } |
837 | _selectablesWithSelections = null; |
838 | super.dispose(); |
839 | } |
840 | |
841 | @override |
842 | Widget build(BuildContext context) { |
843 | super.build(context); |
844 | if (_registrar == null) { |
845 | return widget.child; |
846 | } |
847 | return SelectionRegistrarScope( |
848 | registrar: this, |
849 | child: widget.child, |
850 | ); |
851 | } |
852 | } |
853 | |
854 | // Return a Widget for the given Exception |
855 | Widget _createErrorWidget(Object exception, StackTrace stackTrace) { |
856 | final FlutterErrorDetails details = FlutterErrorDetails( |
857 | exception: exception, |
858 | stack: stackTrace, |
859 | library: 'widgets library' , |
860 | context: ErrorDescription('building' ), |
861 | ); |
862 | FlutterError.reportError(details); |
863 | return ErrorWidget.builder(details); |
864 | } |
865 | |
866 | /// A delegate that supplies children for scrolling in two dimensions. |
867 | /// |
868 | /// A [TwoDimensionalScrollView] lazily constructs its box children to avoid |
869 | /// creating more children than are visible through the |
870 | /// [TwoDimensionalViewport]. Rather than receiving children as an |
871 | /// explicit [List], it receives its children using a |
872 | /// [TwoDimensionalChildDelegate]. |
873 | /// |
874 | /// As a ChangeNotifier, this delegate allows subclasses to notify its listeners |
875 | /// (typically as a subclass of [RenderTwoDimensionalViewport]) to rebuild when |
876 | /// aspects of the delegate change. When values returned by getters or builders |
877 | /// on this delegate change, [notifyListeners] should be called. This signals to |
878 | /// the [RenderTwoDimensionalViewport] that the getters and builders need to be |
879 | /// re-queried to update the layout of children in the viewport. |
880 | /// |
881 | /// See also: |
882 | /// |
883 | /// * [TwoDimensionalChildBuilderDelegate], an concrete subclass of this that |
884 | /// lazily builds children on demand. |
885 | /// * [TwoDimensionalChildListDelegate], an concrete subclass of this that |
886 | /// uses a two dimensional array to layout children. |
887 | abstract class TwoDimensionalChildDelegate extends ChangeNotifier { |
888 | /// Returns the child with the given [ChildVicinity], which is described in |
889 | /// terms of x and y indices. |
890 | /// |
891 | /// Subclasses must implement this function and will typically wrap their |
892 | /// children in [RepaintBoundary] widgets. |
893 | /// |
894 | /// The values returned by this method are cached. To indicate that the |
895 | /// widgets have changed, a new delegate must be provided, and the new |
896 | /// delegate's [shouldRebuild] method must return true. Alternatively, |
897 | /// calling [notifyListeners] will allow the same delegate to be used. |
898 | Widget? build(BuildContext context, ChildVicinity vicinity); |
899 | |
900 | /// Called whenever a new instance of the child delegate class is |
901 | /// provided. |
902 | /// |
903 | /// If the new instance represents different information than the old |
904 | /// instance, then the method should return true, otherwise it should return |
905 | /// false. |
906 | /// |
907 | /// If the method returns false, then the [build] call might be optimized |
908 | /// away. |
909 | bool shouldRebuild(covariant TwoDimensionalChildDelegate oldDelegate); |
910 | } |
911 | |
912 | /// A delegate that supplies children for a [TwoDimensionalScrollView] using a |
913 | /// builder callback. |
914 | /// |
915 | /// The widgets returned from the builder callback are automatically wrapped in |
916 | /// [RepaintBoundary] widgets if [addRepaintBoundaries] is true |
917 | /// (also the default). |
918 | /// |
919 | /// See also: |
920 | /// |
921 | /// * [TwoDimensionalChildListDelegate], which is a similar delegate that has an |
922 | /// explicit two dimensional array of children. |
923 | /// * [SliverChildBuilderDelegate], which is a delegate that uses a builder |
924 | /// callback to construct the children in one dimension instead of two. |
925 | /// * [SliverChildListDelegate], which is a delegate that has an explicit list |
926 | /// of children in one dimension instead of two. |
927 | class TwoDimensionalChildBuilderDelegate extends TwoDimensionalChildDelegate { |
928 | /// Creates a delegate that supplies children for a [TwoDimensionalScrollView] |
929 | /// using the given builder callback. |
930 | TwoDimensionalChildBuilderDelegate({ |
931 | required this.builder, |
932 | int? maxXIndex, |
933 | int? maxYIndex, |
934 | this.addRepaintBoundaries = true, |
935 | this.addAutomaticKeepAlives = true, |
936 | }) : assert(maxYIndex == null || maxYIndex >= -1), |
937 | assert(maxXIndex == null || maxXIndex >= -1), |
938 | _maxYIndex = maxYIndex, |
939 | _maxXIndex = maxXIndex; |
940 | |
941 | /// Called to build children on demand. |
942 | /// |
943 | /// Implementors of [RenderTwoDimensionalViewport.layoutChildSequence] |
944 | /// call this builder to create the children of the viewport. For |
945 | /// [ChildVicinity] indices greater than [maxXIndex] or [maxYIndex], null will |
946 | /// be returned by the default [build] implementation. This default behavior |
947 | /// can be changed by overriding the build method. |
948 | /// |
949 | /// Must return null if asked to build a widget with a [ChildVicinity] that |
950 | /// does not exist. |
951 | /// |
952 | /// The delegate wraps the children returned by this builder in |
953 | /// [RepaintBoundary] widgets if [addRepaintBoundaries] is true. |
954 | final TwoDimensionalIndexedWidgetBuilder builder; |
955 | |
956 | /// The maximum [ChildVicinity.xIndex] for children in the x axis. |
957 | /// |
958 | /// {@template flutter.widgets.twoDimensionalChildBuilderDelegate.maxIndex} |
959 | /// For each [ChildVicinity], the child's relative location is described in |
960 | /// terms of x and y indices to facilitate a consistent visitor pattern for |
961 | /// all children in the viewport. |
962 | /// |
963 | /// This is fairly straightforward in the context of a table implementation, |
964 | /// where there is usually the same number of columns in every row and vice |
965 | /// versa, each aligned one after the other. |
966 | /// |
967 | /// When plotting children more abstractly in two dimensional space, there may |
968 | /// be more x indices for a given y index than another y index. An example of |
969 | /// this would be a scatter plot where there are more children at the top of |
970 | /// the graph than at the bottom. |
971 | /// |
972 | /// If null, subclasses of [RenderTwoDimensionalViewport] can continue call on |
973 | /// the [builder] until null has been returned for each known index of x and |
974 | /// y. In some cases, null may not be a terminating result, such as a table |
975 | /// with a merged cell spanning multiple indices. Refer to the |
976 | /// [TwoDimensionalViewport] subclass to learn how this value is applied in |
977 | /// the specific use case. |
978 | /// |
979 | /// If not null, the value must be greater than or equal to -1, where -1 |
980 | /// indicates there will be no children at all provided to the |
981 | /// [TwoDimensionalViewport]. |
982 | /// |
983 | /// If the value changes, the delegate will call [notifyListeners]. This |
984 | /// informs the [RenderTwoDimensionalViewport] that any cached information |
985 | /// from the delegate is invalid. |
986 | /// {@endtemplate} |
987 | /// |
988 | /// This value represents the greatest x index of all [ChildVicinity]s for the |
989 | /// two dimensional scroll view. |
990 | /// |
991 | /// See also: |
992 | /// |
993 | /// * [RenderTwoDimensionalViewport.buildOrObtainChildFor], the method that |
994 | /// leads to calling on the delegate to build a child of the given |
995 | /// [ChildVicinity]. |
996 | int? get maxXIndex => _maxXIndex; |
997 | int? _maxXIndex; |
998 | set maxXIndex(int? value) { |
999 | if (value == maxXIndex) { |
1000 | return; |
1001 | } |
1002 | assert(value == null || value >= -1); |
1003 | _maxXIndex = value; |
1004 | notifyListeners(); |
1005 | } |
1006 | |
1007 | /// The maximum [ChildVicinity.yIndex] for children in the y axis. |
1008 | /// |
1009 | /// {@macro flutter.widgets.twoDimensionalChildBuilderDelegate.maxIndex} |
1010 | /// |
1011 | /// This value represents the greatest y index of all [ChildVicinity]s for the |
1012 | /// two dimensional scroll view. |
1013 | /// |
1014 | /// See also: |
1015 | /// |
1016 | /// * [RenderTwoDimensionalViewport.buildOrObtainChildFor], the method that |
1017 | /// leads to calling on the delegate to build a child of the given |
1018 | /// [ChildVicinity]. |
1019 | int? get maxYIndex => _maxYIndex; |
1020 | int? _maxYIndex; |
1021 | set maxYIndex(int? value) { |
1022 | if (maxYIndex == value) { |
1023 | return; |
1024 | } |
1025 | assert(value == null || value >= -1); |
1026 | _maxYIndex = value; |
1027 | notifyListeners(); |
1028 | } |
1029 | |
1030 | /// {@macro flutter.widgets.SliverChildBuilderDelegate.addRepaintBoundaries} |
1031 | final bool addRepaintBoundaries; |
1032 | |
1033 | /// {@macro flutter.widgets.SliverChildBuilderDelegate.addAutomaticKeepAlives} |
1034 | final bool addAutomaticKeepAlives; |
1035 | |
1036 | @override |
1037 | Widget? build(BuildContext context, ChildVicinity vicinity) { |
1038 | // If we have exceeded explicit upper bounds, return null. |
1039 | if (vicinity.xIndex < 0 || (maxXIndex != null && vicinity.xIndex > maxXIndex!)) { |
1040 | return null; |
1041 | } |
1042 | if (vicinity.yIndex < 0 || (maxYIndex != null && vicinity.yIndex > maxYIndex!)) { |
1043 | return null; |
1044 | } |
1045 | |
1046 | Widget? child; |
1047 | try { |
1048 | child = builder(context, vicinity); |
1049 | } catch (exception, stackTrace) { |
1050 | child = _createErrorWidget(exception, stackTrace); |
1051 | } |
1052 | if (child == null) { |
1053 | return null; |
1054 | } |
1055 | if (addRepaintBoundaries) { |
1056 | child = RepaintBoundary(child: child); |
1057 | } |
1058 | if (addAutomaticKeepAlives) { |
1059 | child = AutomaticKeepAlive(child: _SelectionKeepAlive(child: child)); |
1060 | } |
1061 | return child; |
1062 | } |
1063 | |
1064 | @override |
1065 | bool shouldRebuild(covariant TwoDimensionalChildDelegate oldDelegate) => true; |
1066 | } |
1067 | |
1068 | /// A delegate that supplies children for a [TwoDimensionalViewport] using an |
1069 | /// explicit two dimensional array. |
1070 | /// |
1071 | /// In general, building all the widgets in advance is not efficient. It is |
1072 | /// better to create a delegate that builds them on demand using |
1073 | /// [TwoDimensionalChildBuilderDelegate] or by subclassing |
1074 | /// [TwoDimensionalChildDelegate] directly. |
1075 | /// |
1076 | /// This class is provided for the cases where either the list of children is |
1077 | /// known well in advance (ideally the children are themselves compile-time |
1078 | /// constants, for example), and therefore will not be built each time the |
1079 | /// delegate itself is created, or the array is small, such that it's likely |
1080 | /// always visible (and thus there is nothing to be gained by building it on |
1081 | /// demand). |
1082 | /// |
1083 | /// The widgets in the given [children] list are automatically wrapped in |
1084 | /// [RepaintBoundary] widgets if [addRepaintBoundaries] is true |
1085 | /// (also the default). |
1086 | /// |
1087 | /// The [children] are accessed for each [ChildVicinity.yIndex] and |
1088 | /// [ChildVicinity.xIndex] of the [TwoDimensionalViewport] as |
1089 | /// `children[vicinity.yIndex][vicinity.xIndex]`. |
1090 | /// |
1091 | /// See also: |
1092 | /// |
1093 | /// * [TwoDimensionalChildBuilderDelegate], which is a delegate that uses a |
1094 | /// builder callback to construct the children. |
1095 | /// * [SliverChildBuilderDelegate], which is a delegate that uses a builder |
1096 | /// callback to construct the children in one dimension instead of two. |
1097 | /// * [SliverChildListDelegate], which is a delegate that has an explicit list |
1098 | /// of children in one dimension instead of two. |
1099 | class TwoDimensionalChildListDelegate extends TwoDimensionalChildDelegate { |
1100 | /// Creates a delegate that supplies children for a [TwoDimensionalScrollView]. |
1101 | /// |
1102 | /// The [children] and [addRepaintBoundaries] must not be |
1103 | /// null. |
1104 | TwoDimensionalChildListDelegate({ |
1105 | this.addRepaintBoundaries = true, |
1106 | this.addAutomaticKeepAlives = true, |
1107 | required this.children, |
1108 | }); |
1109 | |
1110 | /// The widgets to display. |
1111 | /// |
1112 | /// Also, a [Widget] in Flutter is immutable, so directly modifying the |
1113 | /// [children] such as `someWidget.children.add(...)` or |
1114 | /// passing a reference of the original list value to the [children] parameter |
1115 | /// will result in incorrect behaviors. Whenever the |
1116 | /// children list is modified, a new list object must be provided. |
1117 | /// |
1118 | /// The [children] are accessed for each [ChildVicinity.yIndex] and |
1119 | /// [ChildVicinity.xIndex] of the [TwoDimensionalViewport] as |
1120 | /// `children[vicinity.yIndex][vicinity.xIndex]`. |
1121 | final List<List<Widget>> children; |
1122 | |
1123 | /// {@macro flutter.widgets.SliverChildBuilderDelegate.addRepaintBoundaries} |
1124 | final bool addRepaintBoundaries; |
1125 | |
1126 | /// {@macro flutter.widgets.SliverChildBuilderDelegate.addAutomaticKeepAlives} |
1127 | final bool addAutomaticKeepAlives; |
1128 | |
1129 | @override |
1130 | Widget? build(BuildContext context, ChildVicinity vicinity) { |
1131 | // If we have exceeded explicit upper bounds, return null. |
1132 | if (vicinity.yIndex < 0 || vicinity.yIndex >= children.length) { |
1133 | return null; |
1134 | } |
1135 | if (vicinity.xIndex < 0 || vicinity.xIndex >= children[vicinity.yIndex].length) { |
1136 | return null; |
1137 | } |
1138 | |
1139 | Widget child = children[vicinity.yIndex][vicinity.xIndex]; |
1140 | if (addRepaintBoundaries) { |
1141 | child = RepaintBoundary(child: child); |
1142 | } |
1143 | if (addAutomaticKeepAlives) { |
1144 | child = AutomaticKeepAlive(child: _SelectionKeepAlive(child: child)); |
1145 | } |
1146 | return child; |
1147 | } |
1148 | |
1149 | @override |
1150 | bool shouldRebuild(covariant TwoDimensionalChildListDelegate oldDelegate) { |
1151 | return children != oldDelegate.children; |
1152 | } |
1153 | } |
1154 | |