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 'dart:collection' show HashMap, SplayTreeMap; |
6 | import 'dart:math' as math; |
7 | |
8 | import 'package:flutter/foundation.dart'; |
9 | import 'package:flutter/rendering.dart'; |
10 | |
11 | import 'automatic_keep_alive.dart'; |
12 | import 'basic.dart'; |
13 | import 'framework.dart'; |
14 | import 'scroll_delegate.dart'; |
15 | |
16 | /// A base class for slivers that have [KeepAlive] children. |
17 | /// |
18 | /// See also: |
19 | /// |
20 | /// * [KeepAlive], which marks whether its child widget should be kept alive. |
21 | /// * [SliverChildBuilderDelegate] and [SliverChildListDelegate], slivers |
22 | /// which make use of the keep alive functionality through the |
23 | /// `addAutomaticKeepAlives` property. |
24 | /// * [SliverGrid] and [SliverList], two sliver widgets that are commonly |
25 | /// wrapped with [KeepAlive] widgets to preserve their sliver child subtrees. |
26 | abstract class SliverWithKeepAliveWidget extends RenderObjectWidget { |
27 | /// Initializes fields for subclasses. |
28 | const SliverWithKeepAliveWidget({ |
29 | super.key, |
30 | }); |
31 | |
32 | @override |
33 | RenderSliverWithKeepAliveMixin createRenderObject(BuildContext context); |
34 | } |
35 | |
36 | /// A base class for slivers that have multiple box children. |
37 | /// |
38 | /// Helps subclasses build their children lazily using a [SliverChildDelegate]. |
39 | /// |
40 | /// The widgets returned by the [delegate] are cached and the delegate is only |
41 | /// consulted again if it changes and the new delegate's |
42 | /// [SliverChildDelegate.shouldRebuild] method returns true. |
43 | abstract class SliverMultiBoxAdaptorWidget extends SliverWithKeepAliveWidget { |
44 | /// Initializes fields for subclasses. |
45 | const SliverMultiBoxAdaptorWidget({ |
46 | super.key, |
47 | required this.delegate, |
48 | }); |
49 | |
50 | /// {@template flutter.widgets.SliverMultiBoxAdaptorWidget.delegate} |
51 | /// The delegate that provides the children for this widget. |
52 | /// |
53 | /// The children are constructed lazily using this delegate to avoid creating |
54 | /// more children than are visible through the [Viewport]. |
55 | /// |
56 | /// ## Using more than one delegate in a [Viewport] |
57 | /// |
58 | /// If multiple delegates are used in a single scroll view, the first child of |
59 | /// each delegate will always be laid out, even if it extends beyond the |
60 | /// currently viewable area. This is because at least one child is required in |
61 | /// order to estimate the max scroll offset for the whole scroll view, as it |
62 | /// uses the currently built children to estimate the remaining children's |
63 | /// extent. |
64 | /// |
65 | /// See also: |
66 | /// |
67 | /// * [SliverChildBuilderDelegate] and [SliverChildListDelegate], which are |
68 | /// commonly used subclasses of [SliverChildDelegate] that use a builder |
69 | /// callback and an explicit child list, respectively. |
70 | /// {@endtemplate} |
71 | final SliverChildDelegate delegate; |
72 | |
73 | @override |
74 | SliverMultiBoxAdaptorElement createElement() => SliverMultiBoxAdaptorElement(this); |
75 | |
76 | @override |
77 | RenderSliverMultiBoxAdaptor createRenderObject(BuildContext context); |
78 | |
79 | /// Returns an estimate of the max scroll extent for all the children. |
80 | /// |
81 | /// Subclasses should override this function if they have additional |
82 | /// information about their max scroll extent. |
83 | /// |
84 | /// This is used by [SliverMultiBoxAdaptorElement] to implement part of the |
85 | /// [RenderSliverBoxChildManager] API. |
86 | /// |
87 | /// The default implementation defers to [delegate] via its |
88 | /// [SliverChildDelegate.estimateMaxScrollOffset] method. |
89 | double? estimateMaxScrollOffset( |
90 | SliverConstraints? constraints, |
91 | int firstIndex, |
92 | int lastIndex, |
93 | double leadingScrollOffset, |
94 | double trailingScrollOffset, |
95 | ) { |
96 | assert(lastIndex >= firstIndex); |
97 | return delegate.estimateMaxScrollOffset( |
98 | firstIndex, |
99 | lastIndex, |
100 | leadingScrollOffset, |
101 | trailingScrollOffset, |
102 | ); |
103 | } |
104 | |
105 | @override |
106 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
107 | super.debugFillProperties(properties); |
108 | properties.add(DiagnosticsProperty<SliverChildDelegate>('delegate' , delegate)); |
109 | } |
110 | } |
111 | |
112 | /// A sliver that places multiple box children in a linear array along the main |
113 | /// axis. |
114 | /// |
115 | /// _To learn more about slivers, see [CustomScrollView.slivers]._ |
116 | /// |
117 | /// Each child is forced to have the [SliverConstraints.crossAxisExtent] in the |
118 | /// cross axis but determines its own main axis extent. |
119 | /// |
120 | /// [SliverList] determines its scroll offset by "dead reckoning" because |
121 | /// children outside the visible part of the sliver are not materialized, which |
122 | /// means [SliverList] cannot learn their main axis extent. Instead, newly |
123 | /// materialized children are placed adjacent to existing children. |
124 | /// |
125 | /// {@youtube 560 315 https://www.youtube.com/watch?v=ORiTTaVY6mM} |
126 | /// |
127 | /// If the children have a fixed extent in the main axis, consider using |
128 | /// [SliverFixedExtentList] rather than [SliverList] because |
129 | /// [SliverFixedExtentList] does not need to perform layout on its children to |
130 | /// obtain their extent in the main axis and is therefore more efficient. |
131 | /// |
132 | /// {@macro flutter.widgets.SliverChildDelegate.lifecycle} |
133 | /// |
134 | /// See also: |
135 | /// |
136 | /// * <https://flutter.dev/docs/development/ui/advanced/slivers>, a description |
137 | /// of what slivers are and how to use them. |
138 | /// * [SliverFixedExtentList], which is more efficient for children with |
139 | /// the same extent in the main axis. |
140 | /// * [SliverPrototypeExtentList], which is similar to [SliverFixedExtentList] |
141 | /// except that it uses a prototype list item instead of a pixel value to define |
142 | /// the main axis extent of each item. |
143 | /// * [SliverAnimatedList], which animates items added to or removed from a |
144 | /// list. |
145 | /// * [SliverGrid], which places multiple children in a two dimensional grid. |
146 | /// * [SliverAnimatedGrid], a sliver which animates items when they are |
147 | /// inserted into or removed from a grid. |
148 | class SliverList extends SliverMultiBoxAdaptorWidget { |
149 | /// Creates a sliver that places box children in a linear array. |
150 | const SliverList({ |
151 | super.key, |
152 | required super.delegate, |
153 | }); |
154 | |
155 | /// A sliver that places multiple box children in a linear array along the main |
156 | /// axis. |
157 | /// |
158 | /// This constructor is appropriate for sliver lists with a large (or |
159 | /// infinite) number of children because the builder is called only for those |
160 | /// children that are actually visible. |
161 | /// |
162 | /// Providing a non-null `itemCount` improves the ability of the [SliverGrid] |
163 | /// to estimate the maximum scroll extent. |
164 | /// |
165 | /// `itemBuilder` will be called only with indices greater than or equal to |
166 | /// zero and less than `itemCount`. |
167 | /// |
168 | /// {@macro flutter.widgets.ListView.builder.itemBuilder} |
169 | /// |
170 | /// {@macro flutter.widgets.PageView.findChildIndexCallback} |
171 | /// |
172 | /// The `addAutomaticKeepAlives` argument corresponds to the |
173 | /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The |
174 | /// `addRepaintBoundaries` argument corresponds to the |
175 | /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The |
176 | /// `addSemanticIndexes` argument corresponds to the |
177 | /// [SliverChildBuilderDelegate.addSemanticIndexes] property. |
178 | /// |
179 | /// {@tool snippet} |
180 | /// This example, which would be provided in [CustomScrollView.slivers], |
181 | /// shows an infinite number of items in varying shades of blue: |
182 | /// |
183 | /// ```dart |
184 | /// SliverList.builder( |
185 | /// itemBuilder: (BuildContext context, int index) { |
186 | /// return Container( |
187 | /// alignment: Alignment.center, |
188 | /// color: Colors.lightBlue[100 * (index % 9)], |
189 | /// child: Text('list item $index'), |
190 | /// ); |
191 | /// }, |
192 | /// ) |
193 | /// ``` |
194 | /// {@end-tool} |
195 | SliverList.builder({ |
196 | super.key, |
197 | required NullableIndexedWidgetBuilder itemBuilder, |
198 | ChildIndexGetter? findChildIndexCallback, |
199 | int? itemCount, |
200 | bool addAutomaticKeepAlives = true, |
201 | bool addRepaintBoundaries = true, |
202 | bool addSemanticIndexes = true, |
203 | }) : super(delegate: SliverChildBuilderDelegate( |
204 | itemBuilder, |
205 | findChildIndexCallback: findChildIndexCallback, |
206 | childCount: itemCount, |
207 | addAutomaticKeepAlives: addAutomaticKeepAlives, |
208 | addRepaintBoundaries: addRepaintBoundaries, |
209 | addSemanticIndexes: addSemanticIndexes, |
210 | )); |
211 | |
212 | /// A sliver that places multiple box children, separated by box widgets, in a |
213 | /// linear array along the main axis. |
214 | /// |
215 | /// This constructor is appropriate for sliver lists with a large (or |
216 | /// infinite) number of children because the builder is called only for those |
217 | /// children that are actually visible. |
218 | /// |
219 | /// Providing a non-null `itemCount` improves the ability of the [SliverGrid] |
220 | /// to estimate the maximum scroll extent. |
221 | /// |
222 | /// `itemBuilder` will be called only with indices greater than or equal to |
223 | /// zero and less than `itemCount`. |
224 | /// |
225 | /// {@macro flutter.widgets.ListView.builder.itemBuilder} |
226 | /// |
227 | /// {@macro flutter.widgets.PageView.findChildIndexCallback} |
228 | /// |
229 | /// |
230 | /// The `separatorBuilder` is similar to `itemBuilder`, except it is the widget |
231 | /// that gets placed between itemBuilder(context, index) and itemBuilder(context, index + 1). |
232 | /// |
233 | /// The `addAutomaticKeepAlives` argument corresponds to the |
234 | /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The |
235 | /// `addRepaintBoundaries` argument corresponds to the |
236 | /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The |
237 | /// `addSemanticIndexes` argument corresponds to the |
238 | /// [SliverChildBuilderDelegate.addSemanticIndexes] property. |
239 | /// |
240 | /// {@tool snippet} |
241 | /// This example shows how to create a [SliverList] whose [Container] items |
242 | /// are separated by [Divider]s. The [SliverList] would be provided in |
243 | /// [CustomScrollView.slivers]. |
244 | /// |
245 | /// ```dart |
246 | /// SliverList.separated( |
247 | /// itemBuilder: (BuildContext context, int index) { |
248 | /// return Container( |
249 | /// alignment: Alignment.center, |
250 | /// color: Colors.lightBlue[100 * (index % 9)], |
251 | /// child: Text('list item $index'), |
252 | /// ); |
253 | /// }, |
254 | /// separatorBuilder: (BuildContext context, int index) => const Divider(), |
255 | /// ) |
256 | /// ``` |
257 | /// {@end-tool} |
258 | SliverList.separated({ |
259 | super.key, |
260 | required NullableIndexedWidgetBuilder itemBuilder, |
261 | ChildIndexGetter? findChildIndexCallback, |
262 | required NullableIndexedWidgetBuilder separatorBuilder, |
263 | int? itemCount, |
264 | bool addAutomaticKeepAlives = true, |
265 | bool addRepaintBoundaries = true, |
266 | bool addSemanticIndexes = true, |
267 | }) : super(delegate: SliverChildBuilderDelegate( |
268 | (BuildContext context, int index) { |
269 | final int itemIndex = index ~/ 2; |
270 | final Widget? widget; |
271 | if (index.isEven) { |
272 | widget = itemBuilder(context, itemIndex); |
273 | } else { |
274 | widget = separatorBuilder(context, itemIndex); |
275 | assert(() { |
276 | if (widget == null) { |
277 | throw FlutterError('separatorBuilder cannot return null.' ); |
278 | } |
279 | return true; |
280 | }()); |
281 | } |
282 | return widget; |
283 | }, |
284 | findChildIndexCallback: findChildIndexCallback, |
285 | childCount: itemCount == null ? null : math.max(0, itemCount * 2 - 1), |
286 | addAutomaticKeepAlives: addAutomaticKeepAlives, |
287 | addRepaintBoundaries: addRepaintBoundaries, |
288 | addSemanticIndexes: addSemanticIndexes, |
289 | semanticIndexCallback: (Widget _, int index) { |
290 | return index.isEven ? index ~/ 2 : null; |
291 | }, |
292 | )); |
293 | |
294 | /// A sliver that places multiple box children in a linear array along the main |
295 | /// axis. |
296 | /// |
297 | /// This constructor uses a list of [Widget]s to build the sliver. |
298 | /// |
299 | /// The `addAutomaticKeepAlives` argument corresponds to the |
300 | /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The |
301 | /// `addRepaintBoundaries` argument corresponds to the |
302 | /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The |
303 | /// `addSemanticIndexes` argument corresponds to the |
304 | /// [SliverChildBuilderDelegate.addSemanticIndexes] property. |
305 | /// |
306 | /// {@tool snippet} |
307 | /// This example, which would be provided in [CustomScrollView.slivers], |
308 | /// shows a list containing two [Text] widgets: |
309 | /// |
310 | /// ```dart |
311 | /// SliverList.list( |
312 | /// children: const <Widget>[ |
313 | /// Text('Hello'), |
314 | /// Text('World!'), |
315 | /// ], |
316 | /// ); |
317 | /// ``` |
318 | /// {@end-tool} |
319 | SliverList.list({ |
320 | super.key, |
321 | required List<Widget> children, |
322 | bool addAutomaticKeepAlives = true, |
323 | bool addRepaintBoundaries = true, |
324 | bool addSemanticIndexes = true, |
325 | }) : super(delegate: SliverChildListDelegate( |
326 | children, |
327 | addAutomaticKeepAlives: addAutomaticKeepAlives, |
328 | addRepaintBoundaries: addRepaintBoundaries, |
329 | addSemanticIndexes: addSemanticIndexes, |
330 | )); |
331 | |
332 | @override |
333 | SliverMultiBoxAdaptorElement createElement() => SliverMultiBoxAdaptorElement(this, replaceMovedChildren: true); |
334 | |
335 | @override |
336 | RenderSliverList createRenderObject(BuildContext context) { |
337 | final SliverMultiBoxAdaptorElement element = context as SliverMultiBoxAdaptorElement; |
338 | return RenderSliverList(childManager: element); |
339 | } |
340 | } |
341 | |
342 | /// A sliver that places multiple box children with the same main axis extent in |
343 | /// a linear array. |
344 | /// |
345 | /// _To learn more about slivers, see [CustomScrollView.slivers]._ |
346 | /// |
347 | /// [SliverFixedExtentList] places its children in a linear array along the main |
348 | /// axis starting at offset zero and without gaps. Each child is forced to have |
349 | /// the [itemExtent] in the main axis and the |
350 | /// [SliverConstraints.crossAxisExtent] in the cross axis. |
351 | /// |
352 | /// [SliverFixedExtentList] is more efficient than [SliverList] because |
353 | /// [SliverFixedExtentList] does not need to perform layout on its children to |
354 | /// obtain their extent in the main axis. |
355 | /// |
356 | /// {@tool snippet} |
357 | /// |
358 | /// This example, which would be inserted into a [CustomScrollView.slivers] |
359 | /// list, shows an infinite number of items in varying shades of blue: |
360 | /// |
361 | /// ```dart |
362 | /// SliverFixedExtentList( |
363 | /// itemExtent: 50.0, |
364 | /// delegate: SliverChildBuilderDelegate( |
365 | /// (BuildContext context, int index) { |
366 | /// return Container( |
367 | /// alignment: Alignment.center, |
368 | /// color: Colors.lightBlue[100 * (index % 9)], |
369 | /// child: Text('list item $index'), |
370 | /// ); |
371 | /// }, |
372 | /// ), |
373 | /// ) |
374 | /// ``` |
375 | /// {@end-tool} |
376 | /// |
377 | /// {@macro flutter.widgets.SliverChildDelegate.lifecycle} |
378 | /// |
379 | /// See also: |
380 | /// |
381 | /// * [SliverPrototypeExtentList], which is similar to [SliverFixedExtentList] |
382 | /// except that it uses a prototype list item instead of a pixel value to define |
383 | /// the main axis extent of each item. |
384 | /// * [SliverFillViewport], which determines the [itemExtent] based on |
385 | /// [SliverConstraints.viewportMainAxisExtent]. |
386 | /// * [SliverList], which does not require its children to have the same |
387 | /// extent in the main axis. |
388 | class SliverFixedExtentList extends SliverMultiBoxAdaptorWidget { |
389 | /// Creates a sliver that places box children with the same main axis extent |
390 | /// in a linear array. |
391 | const SliverFixedExtentList({ |
392 | super.key, |
393 | required super.delegate, |
394 | required this.itemExtent, |
395 | }); |
396 | |
397 | /// A sliver that places multiple box children in a linear array along the main |
398 | /// axis. |
399 | /// |
400 | /// [SliverFixedExtentList] places its children in a linear array along the main |
401 | /// axis starting at offset zero and without gaps. Each child is forced to have |
402 | /// the [itemExtent] in the main axis and the |
403 | /// [SliverConstraints.crossAxisExtent] in the cross axis. |
404 | /// |
405 | /// This constructor is appropriate for sliver lists with a large (or |
406 | /// infinite) number of children whose extent is already determined. |
407 | /// |
408 | /// Providing a non-null `itemCount` improves the ability of the [SliverGrid] |
409 | /// to estimate the maximum scroll extent. |
410 | /// |
411 | /// `itemBuilder` will be called only with indices greater than or equal to |
412 | /// zero and less than `itemCount`. |
413 | /// |
414 | /// {@macro flutter.widgets.ListView.builder.itemBuilder} |
415 | /// |
416 | /// The `itemExtent` argument is the extent of each item. |
417 | /// |
418 | /// {@macro flutter.widgets.PageView.findChildIndexCallback} |
419 | /// |
420 | /// The `addAutomaticKeepAlives` argument corresponds to the |
421 | /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The |
422 | /// `addRepaintBoundaries` argument corresponds to the |
423 | /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The |
424 | /// `addSemanticIndexes` argument corresponds to the |
425 | /// [SliverChildBuilderDelegate.addSemanticIndexes] property. |
426 | /// {@tool snippet} |
427 | /// |
428 | /// This example, which would be inserted into a [CustomScrollView.slivers] |
429 | /// list, shows an infinite number of items in varying shades of blue: |
430 | /// |
431 | /// ```dart |
432 | /// SliverFixedExtentList.builder( |
433 | /// itemExtent: 50.0, |
434 | /// itemBuilder: (BuildContext context, int index) { |
435 | /// return Container( |
436 | /// alignment: Alignment.center, |
437 | /// color: Colors.lightBlue[100 * (index % 9)], |
438 | /// child: Text('list item $index'), |
439 | /// ); |
440 | /// }, |
441 | /// ) |
442 | /// ``` |
443 | /// {@end-tool} |
444 | SliverFixedExtentList.builder({ |
445 | super.key, |
446 | required NullableIndexedWidgetBuilder itemBuilder, |
447 | required this.itemExtent, |
448 | ChildIndexGetter? findChildIndexCallback, |
449 | int? itemCount, |
450 | bool addAutomaticKeepAlives = true, |
451 | bool addRepaintBoundaries = true, |
452 | bool addSemanticIndexes = true, |
453 | }) : super(delegate: SliverChildBuilderDelegate( |
454 | itemBuilder, |
455 | findChildIndexCallback: findChildIndexCallback, |
456 | childCount: itemCount, |
457 | addAutomaticKeepAlives: addAutomaticKeepAlives, |
458 | addRepaintBoundaries: addRepaintBoundaries, |
459 | addSemanticIndexes: addSemanticIndexes, |
460 | )); |
461 | |
462 | /// A sliver that places multiple box children in a linear array along the main |
463 | /// axis. |
464 | /// |
465 | /// [SliverFixedExtentList] places its children in a linear array along the main |
466 | /// axis starting at offset zero and without gaps. Each child is forced to have |
467 | /// the [itemExtent] in the main axis and the |
468 | /// [SliverConstraints.crossAxisExtent] in the cross axis. |
469 | /// |
470 | /// This constructor uses a list of [Widget]s to build the sliver. |
471 | /// |
472 | /// The `addAutomaticKeepAlives` argument corresponds to the |
473 | /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The |
474 | /// `addRepaintBoundaries` argument corresponds to the |
475 | /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The |
476 | /// `addSemanticIndexes` argument corresponds to the |
477 | /// [SliverChildBuilderDelegate.addSemanticIndexes] property. |
478 | /// |
479 | /// {@tool snippet} |
480 | /// This example, which would be inserted into a [CustomScrollView.slivers] |
481 | /// list, shows an infinite number of items in varying shades of blue: |
482 | /// |
483 | /// ```dart |
484 | /// SliverFixedExtentList.list( |
485 | /// itemExtent: 50.0, |
486 | /// children: const <Widget>[ |
487 | /// Text('Hello'), |
488 | /// Text('World!'), |
489 | /// ], |
490 | /// ); |
491 | /// ``` |
492 | /// {@end-tool} |
493 | SliverFixedExtentList.list({ |
494 | super.key, |
495 | required List<Widget> children, |
496 | required this.itemExtent, |
497 | bool addAutomaticKeepAlives = true, |
498 | bool addRepaintBoundaries = true, |
499 | bool addSemanticIndexes = true, |
500 | }) : super(delegate: SliverChildListDelegate( |
501 | children, |
502 | addAutomaticKeepAlives: addAutomaticKeepAlives, |
503 | addRepaintBoundaries: addRepaintBoundaries, |
504 | addSemanticIndexes: addSemanticIndexes, |
505 | )); |
506 | |
507 | /// The extent the children are forced to have in the main axis. |
508 | final double itemExtent; |
509 | |
510 | @override |
511 | RenderSliverFixedExtentList createRenderObject(BuildContext context) { |
512 | final SliverMultiBoxAdaptorElement element = context as SliverMultiBoxAdaptorElement; |
513 | return RenderSliverFixedExtentList(childManager: element, itemExtent: itemExtent); |
514 | } |
515 | |
516 | @override |
517 | void updateRenderObject(BuildContext context, RenderSliverFixedExtentList renderObject) { |
518 | renderObject.itemExtent = itemExtent; |
519 | } |
520 | } |
521 | |
522 | /// A sliver that places multiple box children in a two dimensional arrangement. |
523 | /// |
524 | /// _To learn more about slivers, see [CustomScrollView.slivers]._ |
525 | /// |
526 | /// [SliverGrid] places its children in arbitrary positions determined by |
527 | /// [gridDelegate]. Each child is forced to have the size specified by the |
528 | /// [gridDelegate]. |
529 | /// |
530 | /// The main axis direction of a grid is the direction in which it scrolls; the |
531 | /// cross axis direction is the orthogonal direction. |
532 | /// |
533 | /// {@youtube 560 315 https://www.youtube.com/watch?v=ORiTTaVY6mM} |
534 | /// |
535 | /// {@tool snippet} |
536 | /// |
537 | /// This example, which would be inserted into a [CustomScrollView.slivers] |
538 | /// list, shows twenty boxes in a pretty teal grid: |
539 | /// |
540 | /// ```dart |
541 | /// SliverGrid( |
542 | /// gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( |
543 | /// maxCrossAxisExtent: 200.0, |
544 | /// mainAxisSpacing: 10.0, |
545 | /// crossAxisSpacing: 10.0, |
546 | /// childAspectRatio: 4.0, |
547 | /// ), |
548 | /// delegate: SliverChildBuilderDelegate( |
549 | /// (BuildContext context, int index) { |
550 | /// return Container( |
551 | /// alignment: Alignment.center, |
552 | /// color: Colors.teal[100 * (index % 9)], |
553 | /// child: Text('grid item $index'), |
554 | /// ); |
555 | /// }, |
556 | /// childCount: 20, |
557 | /// ), |
558 | /// ) |
559 | /// ``` |
560 | /// {@end-tool} |
561 | /// |
562 | /// {@macro flutter.widgets.SliverChildDelegate.lifecycle} |
563 | /// |
564 | /// See also: |
565 | /// |
566 | /// * [SliverList], which places its children in a linear array. |
567 | /// * [SliverFixedExtentList], which places its children in a linear |
568 | /// array with a fixed extent in the main axis. |
569 | /// * [SliverPrototypeExtentList], which is similar to [SliverFixedExtentList] |
570 | /// except that it uses a prototype list item instead of a pixel value to define |
571 | /// the main axis extent of each item. |
572 | class SliverGrid extends SliverMultiBoxAdaptorWidget { |
573 | /// Creates a sliver that places multiple box children in a two dimensional |
574 | /// arrangement. |
575 | const SliverGrid({ |
576 | super.key, |
577 | required super.delegate, |
578 | required this.gridDelegate, |
579 | }); |
580 | |
581 | /// A sliver that creates a 2D array of widgets that are created on demand. |
582 | /// |
583 | /// This constructor is appropriate for sliver grids with a large (or |
584 | /// infinite) number of children because the builder is called only for those |
585 | /// children that are actually visible. |
586 | /// |
587 | /// Providing a non-null `itemCount` improves the ability of the [SliverGrid] |
588 | /// to estimate the maximum scroll extent. |
589 | /// |
590 | /// `itemBuilder` will be called only with indices greater than or equal to |
591 | /// zero and less than `itemCount`. |
592 | /// |
593 | /// {@macro flutter.widgets.ListView.builder.itemBuilder} |
594 | /// |
595 | /// {@macro flutter.widgets.PageView.findChildIndexCallback} |
596 | /// |
597 | /// The [gridDelegate] argument is required. |
598 | /// |
599 | /// The `addAutomaticKeepAlives` argument corresponds to the |
600 | /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The |
601 | /// `addRepaintBoundaries` argument corresponds to the |
602 | /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The |
603 | /// `addSemanticIndexes` argument corresponds to the |
604 | /// [SliverChildBuilderDelegate.addSemanticIndexes] property. |
605 | SliverGrid.builder({ |
606 | super.key, |
607 | required this.gridDelegate, |
608 | required NullableIndexedWidgetBuilder itemBuilder, |
609 | ChildIndexGetter? findChildIndexCallback, |
610 | int? itemCount, |
611 | bool addAutomaticKeepAlives = true, |
612 | bool addRepaintBoundaries = true, |
613 | bool addSemanticIndexes = true, |
614 | }) : super(delegate: SliverChildBuilderDelegate( |
615 | itemBuilder, |
616 | findChildIndexCallback: findChildIndexCallback, |
617 | childCount: itemCount, |
618 | addAutomaticKeepAlives: addAutomaticKeepAlives, |
619 | addRepaintBoundaries: addRepaintBoundaries, |
620 | addSemanticIndexes: addSemanticIndexes, |
621 | )); |
622 | |
623 | /// Creates a sliver that places multiple box children in a two dimensional |
624 | /// arrangement with a fixed number of tiles in the cross axis. |
625 | /// |
626 | /// Uses a [SliverGridDelegateWithFixedCrossAxisCount] as the [gridDelegate], |
627 | /// and a [SliverChildListDelegate] as the [delegate]. |
628 | /// |
629 | /// See also: |
630 | /// |
631 | /// * [GridView.count], the equivalent constructor for [GridView] widgets. |
632 | SliverGrid.count({ |
633 | super.key, |
634 | required int crossAxisCount, |
635 | double mainAxisSpacing = 0.0, |
636 | double crossAxisSpacing = 0.0, |
637 | double childAspectRatio = 1.0, |
638 | List<Widget> children = const <Widget>[], |
639 | }) : gridDelegate = SliverGridDelegateWithFixedCrossAxisCount( |
640 | crossAxisCount: crossAxisCount, |
641 | mainAxisSpacing: mainAxisSpacing, |
642 | crossAxisSpacing: crossAxisSpacing, |
643 | childAspectRatio: childAspectRatio, |
644 | ), |
645 | super(delegate: SliverChildListDelegate(children)); |
646 | |
647 | /// Creates a sliver that places multiple box children in a two dimensional |
648 | /// arrangement with tiles that each have a maximum cross-axis extent. |
649 | /// |
650 | /// Uses a [SliverGridDelegateWithMaxCrossAxisExtent] as the [gridDelegate], |
651 | /// and a [SliverChildListDelegate] as the [delegate]. |
652 | /// |
653 | /// See also: |
654 | /// |
655 | /// * [GridView.extent], the equivalent constructor for [GridView] widgets. |
656 | SliverGrid.extent({ |
657 | super.key, |
658 | required double maxCrossAxisExtent, |
659 | double mainAxisSpacing = 0.0, |
660 | double crossAxisSpacing = 0.0, |
661 | double childAspectRatio = 1.0, |
662 | List<Widget> children = const <Widget>[], |
663 | }) : gridDelegate = SliverGridDelegateWithMaxCrossAxisExtent( |
664 | maxCrossAxisExtent: maxCrossAxisExtent, |
665 | mainAxisSpacing: mainAxisSpacing, |
666 | crossAxisSpacing: crossAxisSpacing, |
667 | childAspectRatio: childAspectRatio, |
668 | ), |
669 | super(delegate: SliverChildListDelegate(children)); |
670 | |
671 | /// The delegate that controls the size and position of the children. |
672 | final SliverGridDelegate gridDelegate; |
673 | |
674 | @override |
675 | RenderSliverGrid createRenderObject(BuildContext context) { |
676 | final SliverMultiBoxAdaptorElement element = context as SliverMultiBoxAdaptorElement; |
677 | return RenderSliverGrid(childManager: element, gridDelegate: gridDelegate); |
678 | } |
679 | |
680 | @override |
681 | void updateRenderObject(BuildContext context, RenderSliverGrid renderObject) { |
682 | renderObject.gridDelegate = gridDelegate; |
683 | } |
684 | |
685 | @override |
686 | double estimateMaxScrollOffset( |
687 | SliverConstraints? constraints, |
688 | int firstIndex, |
689 | int lastIndex, |
690 | double leadingScrollOffset, |
691 | double trailingScrollOffset, |
692 | ) { |
693 | return super.estimateMaxScrollOffset( |
694 | constraints, |
695 | firstIndex, |
696 | lastIndex, |
697 | leadingScrollOffset, |
698 | trailingScrollOffset, |
699 | ) ?? gridDelegate.getLayout(constraints!).computeMaxScrollOffset(delegate.estimatedChildCount!); |
700 | } |
701 | } |
702 | |
703 | /// An element that lazily builds children for a [SliverMultiBoxAdaptorWidget]. |
704 | /// |
705 | /// Implements [RenderSliverBoxChildManager], which lets this element manage |
706 | /// the children of subclasses of [RenderSliverMultiBoxAdaptor]. |
707 | class SliverMultiBoxAdaptorElement extends RenderObjectElement implements RenderSliverBoxChildManager { |
708 | /// Creates an element that lazily builds children for the given widget. |
709 | /// |
710 | /// If `replaceMovedChildren` is set to true, a new child is proactively |
711 | /// inflate for the index that was previously occupied by a child that moved |
712 | /// to a new index. The layout offset of the moved child is copied over to the |
713 | /// new child. RenderObjects, that depend on the layout offset of existing |
714 | /// children during [RenderObject.performLayout] should set this to true |
715 | /// (example: [RenderSliverList]). For RenderObjects that figure out the |
716 | /// layout offset of their children without looking at the layout offset of |
717 | /// existing children this should be set to false (example: |
718 | /// [RenderSliverFixedExtentList]) to avoid inflating unnecessary children. |
719 | SliverMultiBoxAdaptorElement(SliverMultiBoxAdaptorWidget super.widget, {bool replaceMovedChildren = false}) |
720 | : _replaceMovedChildren = replaceMovedChildren; |
721 | |
722 | final bool _replaceMovedChildren; |
723 | |
724 | @override |
725 | RenderSliverMultiBoxAdaptor get renderObject => super.renderObject as RenderSliverMultiBoxAdaptor; |
726 | |
727 | @override |
728 | void update(covariant SliverMultiBoxAdaptorWidget newWidget) { |
729 | final SliverMultiBoxAdaptorWidget oldWidget = widget as SliverMultiBoxAdaptorWidget; |
730 | super.update(newWidget); |
731 | final SliverChildDelegate newDelegate = newWidget.delegate; |
732 | final SliverChildDelegate oldDelegate = oldWidget.delegate; |
733 | if (newDelegate != oldDelegate && |
734 | (newDelegate.runtimeType != oldDelegate.runtimeType || newDelegate.shouldRebuild(oldDelegate))) { |
735 | performRebuild(); |
736 | } |
737 | } |
738 | |
739 | final SplayTreeMap<int, Element?> _childElements = SplayTreeMap<int, Element?>(); |
740 | RenderBox? _currentBeforeChild; |
741 | |
742 | @override |
743 | void performRebuild() { |
744 | super.performRebuild(); |
745 | _currentBeforeChild = null; |
746 | bool childrenUpdated = false; |
747 | assert(_currentlyUpdatingChildIndex == null); |
748 | try { |
749 | final SplayTreeMap<int, Element?> newChildren = SplayTreeMap<int, Element?>(); |
750 | final Map<int, double> indexToLayoutOffset = HashMap<int, double>(); |
751 | final SliverMultiBoxAdaptorWidget adaptorWidget = widget as SliverMultiBoxAdaptorWidget; |
752 | void processElement(int index) { |
753 | _currentlyUpdatingChildIndex = index; |
754 | if (_childElements[index] != null && _childElements[index] != newChildren[index]) { |
755 | // This index has an old child that isn't used anywhere and should be deactivated. |
756 | _childElements[index] = updateChild(_childElements[index], null, index); |
757 | childrenUpdated = true; |
758 | } |
759 | final Element? newChild = updateChild(newChildren[index], _build(index, adaptorWidget), index); |
760 | if (newChild != null) { |
761 | childrenUpdated = childrenUpdated || _childElements[index] != newChild; |
762 | _childElements[index] = newChild; |
763 | final SliverMultiBoxAdaptorParentData parentData = newChild.renderObject!.parentData! as SliverMultiBoxAdaptorParentData; |
764 | if (index == 0) { |
765 | parentData.layoutOffset = 0.0; |
766 | } else if (indexToLayoutOffset.containsKey(index)) { |
767 | parentData.layoutOffset = indexToLayoutOffset[index]; |
768 | } |
769 | if (!parentData.keptAlive) { |
770 | _currentBeforeChild = newChild.renderObject as RenderBox?; |
771 | } |
772 | } else { |
773 | childrenUpdated = true; |
774 | _childElements.remove(index); |
775 | } |
776 | } |
777 | for (final int index in _childElements.keys.toList()) { |
778 | final Key? key = _childElements[index]!.widget.key; |
779 | final int? newIndex = key == null ? null : adaptorWidget.delegate.findIndexByKey(key); |
780 | final SliverMultiBoxAdaptorParentData? childParentData = |
781 | _childElements[index]!.renderObject?.parentData as SliverMultiBoxAdaptorParentData?; |
782 | |
783 | if (childParentData != null && childParentData.layoutOffset != null) { |
784 | indexToLayoutOffset[index] = childParentData.layoutOffset!; |
785 | } |
786 | |
787 | if (newIndex != null && newIndex != index) { |
788 | // The layout offset of the child being moved is no longer accurate. |
789 | if (childParentData != null) { |
790 | childParentData.layoutOffset = null; |
791 | } |
792 | |
793 | newChildren[newIndex] = _childElements[index]; |
794 | if (_replaceMovedChildren) { |
795 | // We need to make sure the original index gets processed. |
796 | newChildren.putIfAbsent(index, () => null); |
797 | } |
798 | // We do not want the remapped child to get deactivated during processElement. |
799 | _childElements.remove(index); |
800 | } else { |
801 | newChildren.putIfAbsent(index, () => _childElements[index]); |
802 | } |
803 | } |
804 | |
805 | renderObject.debugChildIntegrityEnabled = false; // Moving children will temporary violate the integrity. |
806 | newChildren.keys.forEach(processElement); |
807 | // An element rebuild only updates existing children. The underflow check |
808 | // is here to make sure we look ahead one more child if we were at the end |
809 | // of the child list before the update. By doing so, we can update the max |
810 | // scroll offset during the layout phase. Otherwise, the layout phase may |
811 | // be skipped, and the scroll view may be stuck at the previous max |
812 | // scroll offset. |
813 | // |
814 | // This logic is not needed if any existing children has been updated, |
815 | // because we will not skip the layout phase if that happens. |
816 | if (!childrenUpdated && _didUnderflow) { |
817 | final int lastKey = _childElements.lastKey() ?? -1; |
818 | final int rightBoundary = lastKey + 1; |
819 | newChildren[rightBoundary] = _childElements[rightBoundary]; |
820 | processElement(rightBoundary); |
821 | } |
822 | } finally { |
823 | _currentlyUpdatingChildIndex = null; |
824 | renderObject.debugChildIntegrityEnabled = true; |
825 | } |
826 | } |
827 | |
828 | Widget? _build(int index, SliverMultiBoxAdaptorWidget widget) { |
829 | return widget.delegate.build(this, index); |
830 | } |
831 | |
832 | @override |
833 | void createChild(int index, { required RenderBox? after }) { |
834 | assert(_currentlyUpdatingChildIndex == null); |
835 | owner!.buildScope(this, () { |
836 | final bool insertFirst = after == null; |
837 | assert(insertFirst || _childElements[index-1] != null); |
838 | _currentBeforeChild = insertFirst ? null : (_childElements[index-1]!.renderObject as RenderBox?); |
839 | Element? newChild; |
840 | try { |
841 | final SliverMultiBoxAdaptorWidget adaptorWidget = widget as SliverMultiBoxAdaptorWidget; |
842 | _currentlyUpdatingChildIndex = index; |
843 | newChild = updateChild(_childElements[index], _build(index, adaptorWidget), index); |
844 | } finally { |
845 | _currentlyUpdatingChildIndex = null; |
846 | } |
847 | if (newChild != null) { |
848 | _childElements[index] = newChild; |
849 | } else { |
850 | _childElements.remove(index); |
851 | } |
852 | }); |
853 | } |
854 | |
855 | @override |
856 | Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) { |
857 | final SliverMultiBoxAdaptorParentData? oldParentData = child?.renderObject?.parentData as SliverMultiBoxAdaptorParentData?; |
858 | final Element? newChild = super.updateChild(child, newWidget, newSlot); |
859 | final SliverMultiBoxAdaptorParentData? newParentData = newChild?.renderObject?.parentData as SliverMultiBoxAdaptorParentData?; |
860 | |
861 | // Preserve the old layoutOffset if the renderObject was swapped out. |
862 | if (oldParentData != newParentData && oldParentData != null && newParentData != null) { |
863 | newParentData.layoutOffset = oldParentData.layoutOffset; |
864 | } |
865 | return newChild; |
866 | } |
867 | |
868 | @override |
869 | void forgetChild(Element child) { |
870 | assert(child.slot != null); |
871 | assert(_childElements.containsKey(child.slot)); |
872 | _childElements.remove(child.slot); |
873 | super.forgetChild(child); |
874 | } |
875 | |
876 | @override |
877 | void removeChild(RenderBox child) { |
878 | final int index = renderObject.indexOf(child); |
879 | assert(_currentlyUpdatingChildIndex == null); |
880 | assert(index >= 0); |
881 | owner!.buildScope(this, () { |
882 | assert(_childElements.containsKey(index)); |
883 | try { |
884 | _currentlyUpdatingChildIndex = index; |
885 | final Element? result = updateChild(_childElements[index], null, index); |
886 | assert(result == null); |
887 | } finally { |
888 | _currentlyUpdatingChildIndex = null; |
889 | } |
890 | _childElements.remove(index); |
891 | assert(!_childElements.containsKey(index)); |
892 | }); |
893 | } |
894 | |
895 | static double _extrapolateMaxScrollOffset( |
896 | int firstIndex, |
897 | int lastIndex, |
898 | double leadingScrollOffset, |
899 | double trailingScrollOffset, |
900 | int childCount, |
901 | ) { |
902 | if (lastIndex == childCount - 1) { |
903 | return trailingScrollOffset; |
904 | } |
905 | final int reifiedCount = lastIndex - firstIndex + 1; |
906 | final double averageExtent = (trailingScrollOffset - leadingScrollOffset) / reifiedCount; |
907 | final int remainingCount = childCount - lastIndex - 1; |
908 | return trailingScrollOffset + averageExtent * remainingCount; |
909 | } |
910 | |
911 | @override |
912 | double estimateMaxScrollOffset( |
913 | SliverConstraints? constraints, { |
914 | int? firstIndex, |
915 | int? lastIndex, |
916 | double? leadingScrollOffset, |
917 | double? trailingScrollOffset, |
918 | }) { |
919 | final int? childCount = estimatedChildCount; |
920 | if (childCount == null) { |
921 | return double.infinity; |
922 | } |
923 | return (widget as SliverMultiBoxAdaptorWidget).estimateMaxScrollOffset( |
924 | constraints, |
925 | firstIndex!, |
926 | lastIndex!, |
927 | leadingScrollOffset!, |
928 | trailingScrollOffset!, |
929 | ) ?? _extrapolateMaxScrollOffset( |
930 | firstIndex, |
931 | lastIndex, |
932 | leadingScrollOffset, |
933 | trailingScrollOffset, |
934 | childCount, |
935 | ); |
936 | } |
937 | |
938 | /// The best available estimate of [childCount], or null if no estimate is available. |
939 | /// |
940 | /// This differs from [childCount] in that [childCount] never returns null (and must |
941 | /// not be accessed if the child count is not yet available, meaning the [createChild] |
942 | /// method has not been provided an index that does not create a child). |
943 | /// |
944 | /// See also: |
945 | /// |
946 | /// * [SliverChildDelegate.estimatedChildCount], to which this getter defers. |
947 | int? get estimatedChildCount => (widget as SliverMultiBoxAdaptorWidget).delegate.estimatedChildCount; |
948 | |
949 | @override |
950 | int get childCount { |
951 | int? result = estimatedChildCount; |
952 | if (result == null) { |
953 | // Since childCount was called, we know that we reached the end of |
954 | // the list (as in, _build return null once), so we know that the |
955 | // list is finite. |
956 | // Let's do an open-ended binary search to find the end of the list |
957 | // manually. |
958 | int lo = 0; |
959 | int hi = 1; |
960 | final SliverMultiBoxAdaptorWidget adaptorWidget = widget as SliverMultiBoxAdaptorWidget; |
961 | const int max = kIsWeb |
962 | ? 9007199254740992 // max safe integer on JS (from 0 to this number x != x+1) |
963 | : ((1 << 63) - 1); |
964 | while (_build(hi - 1, adaptorWidget) != null) { |
965 | lo = hi - 1; |
966 | if (hi < max ~/ 2) { |
967 | hi *= 2; |
968 | } else if (hi < max) { |
969 | hi = max; |
970 | } else { |
971 | throw FlutterError( |
972 | 'Could not find the number of children in ${adaptorWidget.delegate}.\n' |
973 | "The childCount getter was called (implying that the delegate's builder returned null " |
974 | 'for a positive index), but even building the child with index $hi (the maximum ' |
975 | 'possible integer) did not return null. Consider implementing childCount to avoid ' |
976 | 'the cost of searching for the final child.' , |
977 | ); |
978 | } |
979 | } |
980 | while (hi - lo > 1) { |
981 | final int mid = (hi - lo) ~/ 2 + lo; |
982 | if (_build(mid - 1, adaptorWidget) == null) { |
983 | hi = mid; |
984 | } else { |
985 | lo = mid; |
986 | } |
987 | } |
988 | result = lo; |
989 | } |
990 | return result; |
991 | } |
992 | |
993 | @override |
994 | void didStartLayout() { |
995 | assert(debugAssertChildListLocked()); |
996 | } |
997 | |
998 | @override |
999 | void didFinishLayout() { |
1000 | assert(debugAssertChildListLocked()); |
1001 | final int firstIndex = _childElements.firstKey() ?? 0; |
1002 | final int lastIndex = _childElements.lastKey() ?? 0; |
1003 | (widget as SliverMultiBoxAdaptorWidget).delegate.didFinishLayout(firstIndex, lastIndex); |
1004 | } |
1005 | |
1006 | int? _currentlyUpdatingChildIndex; |
1007 | |
1008 | @override |
1009 | bool debugAssertChildListLocked() { |
1010 | assert(_currentlyUpdatingChildIndex == null); |
1011 | return true; |
1012 | } |
1013 | |
1014 | @override |
1015 | void didAdoptChild(RenderBox child) { |
1016 | assert(_currentlyUpdatingChildIndex != null); |
1017 | final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData; |
1018 | childParentData.index = _currentlyUpdatingChildIndex; |
1019 | } |
1020 | |
1021 | bool _didUnderflow = false; |
1022 | |
1023 | @override |
1024 | void setDidUnderflow(bool value) { |
1025 | _didUnderflow = value; |
1026 | } |
1027 | |
1028 | @override |
1029 | void insertRenderObjectChild(covariant RenderObject child, int slot) { |
1030 | assert(_currentlyUpdatingChildIndex == slot); |
1031 | assert(renderObject.debugValidateChild(child)); |
1032 | renderObject.insert(child as RenderBox, after: _currentBeforeChild); |
1033 | assert(() { |
1034 | final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData; |
1035 | assert(slot == childParentData.index); |
1036 | return true; |
1037 | }()); |
1038 | } |
1039 | |
1040 | @override |
1041 | void moveRenderObjectChild(covariant RenderObject child, int oldSlot, int newSlot) { |
1042 | assert(_currentlyUpdatingChildIndex == newSlot); |
1043 | renderObject.move(child as RenderBox, after: _currentBeforeChild); |
1044 | } |
1045 | |
1046 | @override |
1047 | void removeRenderObjectChild(covariant RenderObject child, int slot) { |
1048 | assert(_currentlyUpdatingChildIndex != null); |
1049 | renderObject.remove(child as RenderBox); |
1050 | } |
1051 | |
1052 | @override |
1053 | void visitChildren(ElementVisitor visitor) { |
1054 | // The toList() is to make a copy so that the underlying list can be modified by |
1055 | // the visitor: |
1056 | assert(!_childElements.values.any((Element? child) => child == null)); |
1057 | _childElements.values.cast<Element>().toList().forEach(visitor); |
1058 | } |
1059 | |
1060 | @override |
1061 | void debugVisitOnstageChildren(ElementVisitor visitor) { |
1062 | _childElements.values.cast<Element>().where((Element child) { |
1063 | final SliverMultiBoxAdaptorParentData parentData = child.renderObject!.parentData! as SliverMultiBoxAdaptorParentData; |
1064 | final double itemExtent; |
1065 | switch (renderObject.constraints.axis) { |
1066 | case Axis.horizontal: |
1067 | itemExtent = child.renderObject!.paintBounds.width; |
1068 | case Axis.vertical: |
1069 | itemExtent = child.renderObject!.paintBounds.height; |
1070 | } |
1071 | |
1072 | return parentData.layoutOffset != null && |
1073 | parentData.layoutOffset! < renderObject.constraints.scrollOffset + renderObject.constraints.remainingPaintExtent && |
1074 | parentData.layoutOffset! + itemExtent > renderObject.constraints.scrollOffset; |
1075 | }).forEach(visitor); |
1076 | } |
1077 | } |
1078 | |
1079 | /// A sliver widget that makes its sliver child partially transparent. |
1080 | /// |
1081 | /// This class paints its sliver child into an intermediate buffer and then |
1082 | /// blends the sliver back into the scene partially transparent. |
1083 | /// |
1084 | /// For values of opacity other than 0.0 and 1.0, this class is relatively |
1085 | /// expensive because it requires painting the sliver child into an intermediate |
1086 | /// buffer. For the value 0.0, the sliver child is not painted at all. |
1087 | /// For the value 1.0, the sliver child is painted immediately without an |
1088 | /// intermediate buffer. |
1089 | /// |
1090 | /// {@tool dartpad} |
1091 | /// |
1092 | /// This example shows a [SliverList] when the `_visible` member field is true, |
1093 | /// and hides it when it is false. |
1094 | /// |
1095 | /// This is more efficient than adding and removing the sliver child widget from |
1096 | /// the tree on demand, but it does not affect how much the list scrolls (the |
1097 | /// [SliverList] is still present, merely invisible). |
1098 | /// |
1099 | /// ** See code in examples/api/lib/widgets/sliver/sliver_opacity.1.dart ** |
1100 | /// {@end-tool} |
1101 | /// |
1102 | /// See also: |
1103 | /// |
1104 | /// * [Opacity], which can apply a uniform alpha effect to its child using the |
1105 | /// [RenderBox] layout protocol. |
1106 | /// * [AnimatedOpacity], which uses an animation internally to efficiently |
1107 | /// animate [Opacity]. |
1108 | /// * [SliverVisibility], which can hide a child more efficiently (albeit less |
1109 | /// subtly, because it is either visible or hidden, rather than allowing |
1110 | /// fractional opacity values). Specifically, the [SliverVisibility.maintain] |
1111 | /// constructor is equivalent to using a sliver opacity widget with values of |
1112 | /// `0.0` or `1.0`. |
1113 | class SliverOpacity extends SingleChildRenderObjectWidget { |
1114 | /// Creates a sliver that makes its sliver child partially transparent. |
1115 | /// |
1116 | /// The [opacity] argument must be between zero and one, inclusive. |
1117 | const SliverOpacity({ |
1118 | super.key, |
1119 | required this.opacity, |
1120 | this.alwaysIncludeSemantics = false, |
1121 | Widget? sliver, |
1122 | }) : assert(opacity >= 0.0 && opacity <= 1.0), |
1123 | super(child: sliver); |
1124 | |
1125 | /// The fraction to scale the sliver child's alpha value. |
1126 | /// |
1127 | /// An opacity of 1.0 is fully opaque. An opacity of 0.0 is fully transparent |
1128 | /// (i.e. invisible). |
1129 | /// |
1130 | /// Values 1.0 and 0.0 are painted with a fast path. Other values |
1131 | /// require painting the sliver child into an intermediate buffer, which is |
1132 | /// expensive. |
1133 | final double opacity; |
1134 | |
1135 | /// Whether the semantic information of the sliver child is always included. |
1136 | /// |
1137 | /// Defaults to false. |
1138 | /// |
1139 | /// When true, regardless of the opacity settings, the sliver child semantic |
1140 | /// information is exposed as if the widget were fully visible. This is |
1141 | /// useful in cases where labels may be hidden during animations that |
1142 | /// would otherwise contribute relevant semantics. |
1143 | final bool alwaysIncludeSemantics; |
1144 | |
1145 | @override |
1146 | RenderSliverOpacity createRenderObject(BuildContext context) { |
1147 | return RenderSliverOpacity( |
1148 | opacity: opacity, |
1149 | alwaysIncludeSemantics: alwaysIncludeSemantics, |
1150 | ); |
1151 | } |
1152 | |
1153 | @override |
1154 | void updateRenderObject(BuildContext context, RenderSliverOpacity renderObject) { |
1155 | renderObject |
1156 | ..opacity = opacity |
1157 | ..alwaysIncludeSemantics = alwaysIncludeSemantics; |
1158 | } |
1159 | |
1160 | @override |
1161 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
1162 | super.debugFillProperties(properties); |
1163 | properties.add(DiagnosticsProperty<double>('opacity' , opacity)); |
1164 | properties.add(FlagProperty( |
1165 | 'alwaysIncludeSemantics' , |
1166 | value: alwaysIncludeSemantics, |
1167 | ifTrue: 'alwaysIncludeSemantics' , |
1168 | )); |
1169 | } |
1170 | } |
1171 | |
1172 | /// A sliver widget that is invisible during hit testing. |
1173 | /// |
1174 | /// When [ignoring] is true, this widget (and its subtree) is invisible |
1175 | /// to hit testing. It still consumes space during layout and paints its sliver |
1176 | /// child as usual. It just cannot be the target of located events, because it |
1177 | /// returns false from [RenderSliver.hitTest]. |
1178 | /// |
1179 | /// ## Semantics |
1180 | /// |
1181 | /// Using this class may also affect how the semantics subtree underneath is |
1182 | /// collected. |
1183 | /// |
1184 | /// {@macro flutter.widgets.IgnorePointer.semantics} |
1185 | /// |
1186 | /// {@macro flutter.widgets.IgnorePointer.ignoringSemantics} |
1187 | /// |
1188 | /// See also: |
1189 | /// |
1190 | /// * [IgnorePointer], the equivalent widget for boxes. |
1191 | class SliverIgnorePointer extends SingleChildRenderObjectWidget { |
1192 | /// Creates a sliver widget that is invisible to hit testing. |
1193 | const SliverIgnorePointer({ |
1194 | super.key, |
1195 | this.ignoring = true, |
1196 | @Deprecated( |
1197 | 'Create a custom sliver ignore pointer widget instead. ' |
1198 | 'This feature was deprecated after v3.8.0-12.0.pre.' |
1199 | ) |
1200 | this.ignoringSemantics, |
1201 | Widget? sliver, |
1202 | }) : super(child: sliver); |
1203 | |
1204 | /// Whether this sliver is ignored during hit testing. |
1205 | /// |
1206 | /// Regardless of whether this sliver is ignored during hit testing, it will |
1207 | /// still consume space during layout and be visible during painting. |
1208 | /// |
1209 | /// {@macro flutter.widgets.IgnorePointer.semantics} |
1210 | final bool ignoring; |
1211 | |
1212 | /// Whether the semantics of this sliver is ignored when compiling the |
1213 | /// semantics tree. |
1214 | /// |
1215 | /// {@macro flutter.widgets.IgnorePointer.ignoringSemantics} |
1216 | @Deprecated( |
1217 | 'Create a custom sliver ignore pointer widget instead. ' |
1218 | 'This feature was deprecated after v3.8.0-12.0.pre.' |
1219 | ) |
1220 | final bool? ignoringSemantics; |
1221 | |
1222 | @override |
1223 | RenderSliverIgnorePointer createRenderObject(BuildContext context) { |
1224 | return RenderSliverIgnorePointer( |
1225 | ignoring: ignoring, |
1226 | ignoringSemantics: ignoringSemantics, |
1227 | ); |
1228 | } |
1229 | |
1230 | @override |
1231 | void updateRenderObject(BuildContext context, RenderSliverIgnorePointer renderObject) { |
1232 | renderObject |
1233 | ..ignoring = ignoring |
1234 | ..ignoringSemantics = ignoringSemantics; |
1235 | } |
1236 | |
1237 | @override |
1238 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
1239 | super.debugFillProperties(properties); |
1240 | properties.add(DiagnosticsProperty<bool>('ignoring' , ignoring)); |
1241 | properties.add(DiagnosticsProperty<bool>('ignoringSemantics' , ignoringSemantics, defaultValue: null)); |
1242 | } |
1243 | } |
1244 | |
1245 | /// A sliver that lays its sliver child out as if it was in the tree, but |
1246 | /// without painting anything, without making the sliver child available for hit |
1247 | /// testing, and without taking any room in the parent. |
1248 | /// |
1249 | /// Animations continue to run in offstage sliver children, and therefore use |
1250 | /// battery and CPU time, regardless of whether the animations end up being |
1251 | /// visible. |
1252 | /// |
1253 | /// To hide a sliver widget from view while it is |
1254 | /// not needed, prefer removing the widget from the tree entirely rather than |
1255 | /// keeping it alive in an [Offstage] subtree. |
1256 | /// |
1257 | /// See also: |
1258 | /// |
1259 | /// * [Offstage], the equivalent widget for boxes. |
1260 | class SliverOffstage extends SingleChildRenderObjectWidget { |
1261 | /// Creates a sliver that visually hides its sliver child. |
1262 | const SliverOffstage({ |
1263 | super.key, |
1264 | this.offstage = true, |
1265 | Widget? sliver, |
1266 | }) : super(child: sliver); |
1267 | |
1268 | /// Whether the sliver child is hidden from the rest of the tree. |
1269 | /// |
1270 | /// If true, the sliver child is laid out as if it was in the tree, but |
1271 | /// without painting anything, without making the child available for hit |
1272 | /// testing, and without taking any room in the parent. |
1273 | /// |
1274 | /// If false, the sliver child is included in the tree as normal. |
1275 | final bool offstage; |
1276 | |
1277 | @override |
1278 | RenderSliverOffstage createRenderObject(BuildContext context) => RenderSliverOffstage(offstage: offstage); |
1279 | |
1280 | @override |
1281 | void updateRenderObject(BuildContext context, RenderSliverOffstage renderObject) { |
1282 | renderObject.offstage = offstage; |
1283 | } |
1284 | |
1285 | @override |
1286 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
1287 | super.debugFillProperties(properties); |
1288 | properties.add(DiagnosticsProperty<bool>('offstage' , offstage)); |
1289 | } |
1290 | |
1291 | @override |
1292 | SingleChildRenderObjectElement createElement() => _SliverOffstageElement(this); |
1293 | } |
1294 | |
1295 | class _SliverOffstageElement extends SingleChildRenderObjectElement { |
1296 | _SliverOffstageElement(SliverOffstage super.widget); |
1297 | |
1298 | @override |
1299 | void debugVisitOnstageChildren(ElementVisitor visitor) { |
1300 | if (!(widget as SliverOffstage).offstage) { |
1301 | super.debugVisitOnstageChildren(visitor); |
1302 | } |
1303 | } |
1304 | } |
1305 | |
1306 | /// Mark a child as needing to stay alive even when it's in a lazy list that |
1307 | /// would otherwise remove it. |
1308 | /// |
1309 | /// This widget is for use in a [RenderAbstractViewport]s, such as |
1310 | /// [Viewport] or [TwoDimensionalViewport]. |
1311 | /// |
1312 | /// This widget is rarely used directly. The [SliverChildBuilderDelegate] and |
1313 | /// [SliverChildListDelegate] delegates, used with [SliverList] and |
1314 | /// [SliverGrid], as well as the scroll view counterparts [ListView] and |
1315 | /// [GridView], have an `addAutomaticKeepAlives` feature, which is enabled by |
1316 | /// default, and which causes [AutomaticKeepAlive] widgets to be inserted around |
1317 | /// each child, causing [KeepAlive] widgets to be automatically added and |
1318 | /// configured in response to [KeepAliveNotification]s. |
1319 | /// |
1320 | /// The same `addAutomaticKeepAlives` feature is supported by the |
1321 | /// [TwoDimensionalChildBuilderDelegate] and [TwoDimensionalChildListDelegate]. |
1322 | /// |
1323 | /// Therefore, to keep a widget alive, it is more common to use those |
1324 | /// notifications than to directly deal with [KeepAlive] widgets. |
1325 | /// |
1326 | /// In practice, the simplest way to deal with these notifications is to mix |
1327 | /// [AutomaticKeepAliveClientMixin] into one's [State]. See the documentation |
1328 | /// for that mixin class for details. |
1329 | class KeepAlive extends ParentDataWidget<KeepAliveParentDataMixin> { |
1330 | /// Marks a child as needing to remain alive. |
1331 | const KeepAlive({ |
1332 | super.key, |
1333 | required this.keepAlive, |
1334 | required super.child, |
1335 | }); |
1336 | |
1337 | /// Whether to keep the child alive. |
1338 | /// |
1339 | /// If this is false, it is as if this widget was omitted. |
1340 | final bool keepAlive; |
1341 | |
1342 | @override |
1343 | void applyParentData(RenderObject renderObject) { |
1344 | assert(renderObject.parentData is KeepAliveParentDataMixin); |
1345 | final KeepAliveParentDataMixin parentData = renderObject.parentData! as KeepAliveParentDataMixin; |
1346 | if (parentData.keepAlive != keepAlive) { |
1347 | // No need to redo layout if it became true. |
1348 | parentData.keepAlive = keepAlive; |
1349 | final RenderObject? targetParent = renderObject.parent; |
1350 | if (targetParent is RenderObject && !keepAlive) { |
1351 | targetParent.markNeedsLayout(); |
1352 | } |
1353 | } |
1354 | } |
1355 | |
1356 | // We only return true if [keepAlive] is true, because turning _off_ keep |
1357 | // alive requires a layout to do the garbage collection (but turning it on |
1358 | // requires nothing, since by definition the widget is already alive and won't |
1359 | // go away _unless_ we do a layout). |
1360 | @override |
1361 | bool debugCanApplyOutOfTurn() => keepAlive; |
1362 | |
1363 | @override |
1364 | Type get debugTypicalAncestorWidgetClass => throw FlutterError('Multiple Types are supported, use debugTypicalAncestorWidgetDescription.' ); |
1365 | |
1366 | @override |
1367 | String get debugTypicalAncestorWidgetDescription => 'SliverWithKeepAliveWidget or TwoDimensionalViewport' ; |
1368 | |
1369 | @override |
1370 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
1371 | super.debugFillProperties(properties); |
1372 | properties.add(DiagnosticsProperty<bool>('keepAlive' , keepAlive)); |
1373 | } |
1374 | } |
1375 | |
1376 | /// A sliver that constrains the cross axis extent of its sliver child. |
1377 | /// |
1378 | /// The [SliverConstrainedCrossAxis] takes a [maxExtent] parameter and uses it as |
1379 | /// the cross axis extent of the [SliverConstraints] passed to the sliver child. |
1380 | /// The widget ensures that the [maxExtent] is a nonnegative value. |
1381 | /// |
1382 | /// This is useful when you want to apply a custom cross-axis extent constraint |
1383 | /// to a sliver child, as slivers typically consume the full cross axis extent. |
1384 | /// |
1385 | /// This widget also sets its parent data's [SliverPhysicalParentData.crossAxisFlex] |
1386 | /// to 0, so that it informs [SliverCrossAxisGroup] that it should not flex |
1387 | /// in the cross axis direction. |
1388 | /// |
1389 | /// {@tool dartpad} |
1390 | /// In this sample the [SliverConstrainedCrossAxis] sizes its child so that the |
1391 | /// cross axis extent takes up less space than the actual viewport. |
1392 | /// |
1393 | /// ** See code in examples/api/lib/widgets/sliver/sliver_constrained_cross_axis.0.dart ** |
1394 | /// {@end-tool} |
1395 | /// |
1396 | /// See also: |
1397 | /// |
1398 | /// * [SliverCrossAxisGroup], the widget which makes use of 0 flex factor set by |
1399 | /// this widget. |
1400 | class SliverConstrainedCrossAxis extends StatelessWidget { |
1401 | /// Creates a sliver that constrains the cross axis extent of its sliver child. |
1402 | /// |
1403 | /// The [maxExtent] parameter is required and must be nonnegative. |
1404 | const SliverConstrainedCrossAxis({ |
1405 | super.key, |
1406 | required this.maxExtent, |
1407 | required this.sliver, |
1408 | }); |
1409 | |
1410 | /// The cross axis extent to apply to the sliver child. |
1411 | /// |
1412 | /// This value must be nonnegative. |
1413 | final double maxExtent; |
1414 | |
1415 | /// The widget below this widget in the tree. |
1416 | /// |
1417 | /// Must be a sliver. |
1418 | final Widget sliver; |
1419 | |
1420 | @override |
1421 | Widget build(BuildContext context) { |
1422 | return _SliverZeroFlexParentDataWidget( |
1423 | sliver: _SliverConstrainedCrossAxis( |
1424 | maxExtent: maxExtent, |
1425 | sliver: sliver, |
1426 | ) |
1427 | ); |
1428 | } |
1429 | } |
1430 | class _SliverZeroFlexParentDataWidget extends ParentDataWidget<SliverPhysicalParentData> { |
1431 | const _SliverZeroFlexParentDataWidget({ |
1432 | required Widget sliver, |
1433 | }) : super(child: sliver); |
1434 | |
1435 | @override |
1436 | void applyParentData(RenderObject renderObject) { |
1437 | assert(renderObject.parentData is SliverPhysicalParentData); |
1438 | final SliverPhysicalParentData parentData = renderObject.parentData! as SliverPhysicalParentData; |
1439 | bool needsLayout = false; |
1440 | if (parentData.crossAxisFlex != 0) { |
1441 | parentData.crossAxisFlex = 0; |
1442 | needsLayout = true; |
1443 | } |
1444 | |
1445 | if (needsLayout) { |
1446 | final RenderObject? targetParent = renderObject.parent; |
1447 | if (targetParent is RenderObject) { |
1448 | targetParent.markNeedsLayout(); |
1449 | } |
1450 | |
1451 | } |
1452 | } |
1453 | |
1454 | @override |
1455 | Type get debugTypicalAncestorWidgetClass => SliverCrossAxisGroup; |
1456 | } |
1457 | |
1458 | class _SliverConstrainedCrossAxis extends SingleChildRenderObjectWidget { |
1459 | const _SliverConstrainedCrossAxis({ |
1460 | required this.maxExtent, |
1461 | required Widget sliver, |
1462 | }) : assert(maxExtent >= 0.0), |
1463 | super(child: sliver); |
1464 | |
1465 | /// The cross axis extent to apply to the sliver child. |
1466 | /// |
1467 | /// This value must be nonnegative. |
1468 | final double maxExtent; |
1469 | |
1470 | @override |
1471 | RenderSliverConstrainedCrossAxis createRenderObject(BuildContext context) { |
1472 | return RenderSliverConstrainedCrossAxis(maxExtent: maxExtent); |
1473 | } |
1474 | |
1475 | @override |
1476 | void updateRenderObject(BuildContext context, RenderSliverConstrainedCrossAxis renderObject) { |
1477 | renderObject.maxExtent = maxExtent; |
1478 | } |
1479 | } |
1480 | |
1481 | /// Set a flex factor for allocating space in the cross axis direction. |
1482 | /// |
1483 | /// This is a [ParentDataWidget] to be used in [SliverCrossAxisGroup]. |
1484 | /// After all slivers with null or zero flex (e.g. [SliverConstrainedCrossAxis]) |
1485 | /// are laid out (which should determine their own [SliverGeometry.crossAxisExtent]), |
1486 | /// the remaining space is laid out among the slivers with nonzero flex |
1487 | /// proportionally to their flex value. |
1488 | class SliverCrossAxisExpanded extends ParentDataWidget<SliverPhysicalContainerParentData> { |
1489 | /// Creates an object that assigns a [flex] value to the child sliver. |
1490 | /// |
1491 | /// The provided [flex] value must be greater than 0. |
1492 | const SliverCrossAxisExpanded({ |
1493 | super.key, |
1494 | required this.flex, |
1495 | required Widget sliver, |
1496 | }): assert(flex > 0 && flex < double.infinity), |
1497 | super(child: sliver); |
1498 | |
1499 | /// Flex value for allocating cross axis extent left after laying out the children with |
1500 | /// constrained cross axis. The children with flex values will have the remaining extent |
1501 | /// allocated proportionally to their flex value. This must an integer between |
1502 | /// 0 and infinity, exclusive. |
1503 | final int flex; |
1504 | |
1505 | @override |
1506 | void applyParentData(RenderObject renderObject) { |
1507 | assert(renderObject.parentData is SliverPhysicalContainerParentData); |
1508 | assert(renderObject.parent is RenderSliverCrossAxisGroup); |
1509 | final SliverPhysicalParentData parentData = renderObject.parentData! as SliverPhysicalParentData; |
1510 | bool needsLayout = false; |
1511 | |
1512 | if (parentData.crossAxisFlex != flex) { |
1513 | parentData.crossAxisFlex = flex; |
1514 | needsLayout = true; |
1515 | } |
1516 | |
1517 | if (needsLayout) { |
1518 | final RenderObject? targetParent = renderObject.parent; |
1519 | if (targetParent is RenderObject) { |
1520 | targetParent.markNeedsLayout(); |
1521 | } |
1522 | } |
1523 | } |
1524 | |
1525 | @override |
1526 | Type get debugTypicalAncestorWidgetClass => SliverCrossAxisGroup; |
1527 | } |
1528 | |
1529 | |
1530 | /// A sliver that places multiple sliver children in a linear array along |
1531 | /// the cross axis. |
1532 | /// |
1533 | /// ## Layout algorithm |
1534 | /// |
1535 | /// _This section describes how the framework causes [RenderSliverCrossAxisGroup] |
1536 | /// to position its children._ |
1537 | /// |
1538 | /// Layout for a [RenderSliverCrossAxisGroup] has four steps: |
1539 | /// |
1540 | /// 1. Layout each child with a null or zero flex factor with cross axis constraint |
1541 | /// being whatever cross axis space is remaining after laying out any previous |
1542 | /// sliver. Slivers with null or zero flex factor should determine their own |
1543 | /// [SliverGeometry.crossAxisExtent]. For example, the [SliverConstrainedCrossAxis] |
1544 | /// widget uses either [SliverConstrainedCrossAxis.maxExtent] or |
1545 | /// [SliverConstraints.crossAxisExtent], deciding between whichever is smaller. |
1546 | /// 2. Divide up the remaining cross axis space among the children with non-zero flex |
1547 | /// factors according to their flex factor. For example, a child with a flex |
1548 | /// factor of 2.0 will receive twice the amount of cross axis space as a child |
1549 | /// with a flex factor 1.0. |
1550 | /// 3. Layout each of the remaining children with the cross axis constraint |
1551 | /// allocated in the previous step. |
1552 | /// 4. Set the geometry to that of whichever child has the longest |
1553 | /// [SliverGeometry.scrollExtent] with the [SliverGeometry.crossAxisExtent] adjusted |
1554 | /// to [SliverConstraints.crossAxisExtent]. |
1555 | /// |
1556 | /// {@tool dartpad} |
1557 | /// In this sample the [SliverCrossAxisGroup] sizes its three [children] so that |
1558 | /// the first normal [SliverList] has a flex factor of 1, the second [SliverConstrainedCrossAxis] |
1559 | /// has a flex factor of 0 and a maximum cross axis extent of 200.0, and the third |
1560 | /// [SliverCrossAxisExpanded] has a flex factor of 2. |
1561 | /// |
1562 | /// ** See code in examples/api/lib/widgets/sliver/sliver_cross_axis_group.0.dart ** |
1563 | /// {@end-tool} |
1564 | /// |
1565 | /// See also: |
1566 | /// |
1567 | /// * [SliverCrossAxisExpanded], which is the [ParentDataWidget] for setting a flex |
1568 | /// value to a widget. |
1569 | /// * [SliverConstrainedCrossAxis], which is a [RenderObjectWidget] for setting |
1570 | /// an extent to constrain the widget to. |
1571 | /// * [SliverMainAxisGroup], which is the [RenderObjectWidget] for laying out |
1572 | /// multiple slivers along the main axis. |
1573 | class SliverCrossAxisGroup extends MultiChildRenderObjectWidget { |
1574 | /// Creates a sliver that places sliver children in a linear array along |
1575 | /// the cross axis. |
1576 | const SliverCrossAxisGroup({ |
1577 | super.key, |
1578 | required List<Widget> slivers, |
1579 | }): super(children: slivers); |
1580 | |
1581 | @override |
1582 | RenderSliverCrossAxisGroup createRenderObject(BuildContext context) { |
1583 | return RenderSliverCrossAxisGroup(); |
1584 | } |
1585 | } |
1586 | |
1587 | /// A sliver that places multiple sliver children in a linear array along |
1588 | /// the main axis, one after another. |
1589 | /// |
1590 | /// ## Layout algorithm |
1591 | /// |
1592 | /// _This section describes how the framework causes [RenderSliverMainAxisGroup] |
1593 | /// to position its children._ |
1594 | /// |
1595 | /// Layout for a [RenderSliverMainAxisGroup] has four steps: |
1596 | /// |
1597 | /// 1. Keep track of an offset variable which is the total [SliverGeometry.scrollExtent] |
1598 | /// of the slivers laid out so far. |
1599 | /// 2. To determine the constraints for the next sliver child to layout, calculate the |
1600 | /// amount of paint extent occupied from 0.0 to the offset variable and subtract this from |
1601 | /// [SliverConstraints.remainingPaintExtent] minus to use as the child's |
1602 | /// [SliverConstraints.remainingPaintExtent]. For the [SliverConstraints.scrollOffset], |
1603 | /// take the provided constraint's value and subtract out the offset variable, using |
1604 | /// 0.0 if negative. |
1605 | /// 3. Once we finish laying out all the slivers, this offset variable represents |
1606 | /// the total [SliverGeometry.scrollExtent] of the sliver group. Since it is possible |
1607 | /// for specialized slivers to try to paint itself outside of the bounds of the |
1608 | /// sliver group's scroll extent (see [SliverPersistentHeader]), we must do a |
1609 | /// second pass to set a [SliverPhysicalParentData.paintOffset] to make sure it |
1610 | /// is within the bounds of the sliver group. |
1611 | /// 4. Finally, set the [RenderSliverMainAxisGroup.geometry] with the total |
1612 | /// [SliverGeometry.scrollExtent], [SliverGeometry.paintExtent] calculated from |
1613 | /// the constraints and [SliverGeometry.scrollExtent], and [SliverGeometry.maxPaintExtent]. |
1614 | /// |
1615 | /// {@tool dartpad} |
1616 | /// In this sample the [CustomScrollView] renders a [SliverMainAxisGroup] and a |
1617 | /// [SliverToBoxAdapter] with some content. The [SliverMainAxisGroup] renders a |
1618 | /// [SliverAppBar], [SliverList], and [SliverToBoxAdapter]. Notice that when the |
1619 | /// [SliverMainAxisGroup] goes out of view, so does the pinned [SliverAppBar]. |
1620 | /// |
1621 | /// ** See code in examples/api/lib/widgets/sliver/sliver_main_axis_group.0.dart ** |
1622 | /// {@end-tool} |
1623 | /// |
1624 | /// See also: |
1625 | /// |
1626 | /// * [SliverPersistentHeader], which is a [RenderObjectWidget] which may require |
1627 | /// adjustment to its [SliverPhysicalParentData.paintOffset] to make it fit |
1628 | /// within the computed [SliverGeometry.scrollExtent] of the [SliverMainAxisGroup]. |
1629 | /// * [SliverCrossAxisGroup], which is the [RenderObjectWidget] for laying out |
1630 | /// multiple slivers along the cross axis. |
1631 | class SliverMainAxisGroup extends MultiChildRenderObjectWidget { |
1632 | /// Creates a sliver that places sliver children in a linear array along |
1633 | /// the main axis. |
1634 | const SliverMainAxisGroup({ |
1635 | super.key, |
1636 | required List<Widget> slivers, |
1637 | }) : super(children: slivers); |
1638 | |
1639 | @override |
1640 | RenderSliverMainAxisGroup createRenderObject(BuildContext context) { |
1641 | return RenderSliverMainAxisGroup(); |
1642 | } |
1643 | } |
1644 | |