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