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
5import 'dart:collection' show HashMap, SplayTreeMap;
6import 'dart:math' as math;
7
8import 'package:flutter/foundation.dart';
9import 'package:flutter/rendering.dart';
10
11import 'automatic_keep_alive.dart';
12import 'basic.dart';
13import 'framework.dart';
14import '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.
26abstract 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.
43abstract 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.
148class 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.
388class 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.
572class 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].
707class 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`.
1113class 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.
1191class 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.
1260class 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
1295class _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.
1329class 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.
1400class 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}
1430class _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
1458class _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.
1488class 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.
1573class 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.
1631class 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