// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:collection' show SplayTreeMap, HashMap; import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'automatic_keep_alive.dart'; import 'basic.dart'; import 'framework.dart'; export 'package:flutter/rendering.dart' show SliverGridDelegate, SliverGridDelegateWithFixedCrossAxisCount, SliverGridDelegateWithMaxCrossAxisExtent; // Examples can assume: // late SliverGridDelegateWithMaxCrossAxisExtent _gridDelegate; /// A callback which produces a semantic index given a widget and the local index. /// /// Return a null value to prevent a widget from receiving an index. /// /// A semantic index is used to tag child semantic nodes for accessibility /// announcements in scroll view. /// /// See also: /// /// * [CustomScrollView], for an explanation of scroll semantics. /// * [SliverChildBuilderDelegate], for an explanation of how this is used to /// generate indexes. typedef SemanticIndexCallback = int? Function(Widget widget, int localIndex); int _kDefaultSemanticIndexCallback(Widget _, int localIndex) => localIndex; /// A delegate that supplies children for slivers. /// /// Many slivers lazily construct their box children to avoid creating more /// children than are visible through the [Viewport]. Rather than receiving /// their children as an explicit [List], they receive their children using a /// [SliverChildDelegate]. /// /// It's uncommon to subclass [SliverChildDelegate]. Instead, consider using one /// of the existing subclasses that provide adaptors to builder callbacks or /// explicit child lists. /// /// {@template flutter.widgets.SliverChildDelegate.lifecycle} /// ## Child elements' lifecycle /// /// ### Creation /// /// While laying out the list, visible children's elements, states and render /// objects will be created lazily based on existing widgets (such as in the /// case of [SliverChildListDelegate]) or lazily provided ones (such as in the /// case of [SliverChildBuilderDelegate]). /// /// ### Destruction /// /// When a child is scrolled out of view, the associated element subtree, states /// and render objects are destroyed. A new child at the same position in the /// sliver will be lazily recreated along with new elements, states and render /// objects when it is scrolled back. /// /// ### Destruction mitigation /// /// In order to preserve state as child elements are scrolled in and out of /// view, the following options are possible: /// /// * Moving the ownership of non-trivial UI-state-driving business logic /// out of the sliver child subtree. For instance, if a list contains posts /// with their number of upvotes coming from a cached network response, store /// the list of posts and upvote number in a data model outside the list. Let /// the sliver child UI subtree be easily recreate-able from the /// source-of-truth model object. Use [StatefulWidget]s in the child widget /// subtree to store instantaneous UI state only. /// /// * Letting [KeepAlive] be the root widget of the sliver child widget subtree /// that needs to be preserved. The [KeepAlive] widget marks the child /// subtree's top render object child for keepalive. When the associated top /// render object is scrolled out of view, the sliver keeps the child's /// render object (and by extension, its associated elements and states) in a /// cache list instead of destroying them. When scrolled back into view, the /// render object is repainted as-is (if it wasn't marked dirty in the /// interim). /// /// This only works if the [SliverChildDelegate] subclasses don't wrap the /// child widget subtree with other widgets such as [AutomaticKeepAlive] and /// [RepaintBoundary] via `addAutomaticKeepAlives` and /// `addRepaintBoundaries`. /// /// * Using [AutomaticKeepAlive] widgets (inserted by default in /// [SliverChildListDelegate] or [SliverChildListDelegate]). /// [AutomaticKeepAlive] allows descendant widgets to control whether the /// subtree is actually kept alive or not. This behavior is in contrast with /// [KeepAlive], which will unconditionally keep the subtree alive. /// /// As an example, the [EditableText] widget signals its sliver child element /// subtree to stay alive while its text field has input focus. If it doesn't /// have focus and no other descendants signaled for keepalive via a /// [KeepAliveNotification], the sliver child element subtree will be /// destroyed when scrolled away. /// /// [AutomaticKeepAlive] descendants typically signal it to be kept alive by /// using the [AutomaticKeepAliveClientMixin], then implementing the /// [AutomaticKeepAliveClientMixin.wantKeepAlive] getter and calling /// [AutomaticKeepAliveClientMixin.updateKeepAlive]. /// /// ## Using more than one delegate in a [Viewport] /// /// If multiple delegates are used in a single scroll view, the first child of /// each delegate will always be laid out, even if it extends beyond the /// currently viewable area. This is because at least one child is required in /// order to [estimateMaxScrollOffset] for the whole scroll view, as it uses the /// currently built children to estimate the remaining children's extent. /// {@endtemplate} /// /// See also: /// /// * [SliverChildBuilderDelegate], which is a delegate that uses a builder /// callback to construct the children. /// * [SliverChildListDelegate], which is a delegate that has an explicit list /// of children. abstract class SliverChildDelegate { /// Abstract const constructor. This constructor enables subclasses to provide /// const constructors so that they can be used in const expressions. const SliverChildDelegate(); /// Returns the child with the given index. /// /// Should return null if asked to build a widget with a greater /// index than exists. If this returns null, [estimatedChildCount] /// must subsequently return a precise non-null value (which is then /// used to implement [RenderSliverBoxChildManager.childCount]). /// /// Subclasses typically override this function and wrap their children in /// [AutomaticKeepAlive], [IndexedSemantics], and [RepaintBoundary] widgets. /// /// The values returned by this method are cached. To indicate that the /// widgets have changed, a new delegate must be provided, and the new /// delegate's [shouldRebuild] method must return true. Widget? build(BuildContext context, int index); /// Returns an estimate of the number of children this delegate will build. /// /// Used to estimate the maximum scroll offset if [estimateMaxScrollOffset] /// returns null. /// /// Return null if there are an unbounded number of children or if it would /// be too difficult to estimate the number of children. /// /// This must return a precise number once [build] has returned null, as it /// used to implement [RenderSliverBoxChildManager.childCount]. int? get estimatedChildCount => null; /// Returns an estimate of the max scroll extent for all the children. /// /// Subclasses should override this function if they have additional /// information about their max scroll extent. /// /// The default implementation returns null, which causes the caller to /// extrapolate the max scroll offset from the given parameters. double? estimateMaxScrollOffset( int firstIndex, int lastIndex, double leadingScrollOffset, double trailingScrollOffset, ) => null; /// Called at the end of layout to indicate that layout is now complete. /// /// The `firstIndex` argument is the index of the first child that was /// included in the current layout. The `lastIndex` argument is the index of /// the last child that was included in the current layout. /// /// Useful for subclasses that which to track which children are included in /// the underlying render tree. void didFinishLayout(int firstIndex, int lastIndex) { } /// Called whenever a new instance of the child delegate class is /// provided to the sliver. /// /// If the new instance represents different information than the old /// instance, then the method should return true, otherwise it should return /// false. /// /// If the method returns false, then the [build] call might be optimized /// away. bool shouldRebuild(covariant SliverChildDelegate oldDelegate); /// Find index of child element with associated key. /// /// This will be called during `performRebuild` in [SliverMultiBoxAdaptorElement] /// to check if a child has moved to a different position. It should return the /// index of the child element with associated key, null if not found. /// /// If not provided, a child widget may not map to its existing [RenderObject] /// when the order of children returned from the children builder changes. /// This may result in state-loss. int? findIndexByKey(Key key) => null; @override String toString() { final List<String> description = <String>[]; debugFillDescription(description); return '${describeIdentity(this)}(${description.join(", ")})'; } /// Add additional information to the given description for use by [toString]. @protected @mustCallSuper void debugFillDescription(List<String> description) { try { final int? children = estimatedChildCount; if (children != null) description.add('estimated child count: $children'); } catch (e) { // The exception is forwarded to widget inspector. description.add('estimated child count: EXCEPTION (${e.runtimeType})'); } } } class _SaltedValueKey extends ValueKey<Key> { const _SaltedValueKey(super.key): assert(key != null); } /// Called to find the new index of a child based on its `key` in case of /// reordering. /// /// If the child with the `key` is no longer present, null is returned. /// /// Used by [SliverChildBuilderDelegate.findChildIndexCallback]. typedef ChildIndexGetter = int? Function(Key key); /// A delegate that supplies children for slivers using a builder callback. /// /// Many slivers lazily construct their box children to avoid creating more /// children than are visible through the [Viewport]. This delegate provides /// children using a [NullableIndexedWidgetBuilder] callback, so that the children do /// not even have to be built until they are displayed. /// /// The widgets returned from the builder callback are automatically wrapped in /// [AutomaticKeepAlive] widgets if [addAutomaticKeepAlives] is true (the /// default) and in [RepaintBoundary] widgets if [addRepaintBoundaries] is true /// (also the default). /// /// ## Accessibility /// /// The [CustomScrollView] requires that its semantic children are annotated /// using [IndexedSemantics]. This is done by default in the delegate with /// the `addSemanticIndexes` parameter set to true. /// /// If multiple delegates are used in a single scroll view, then the indexes /// will not be correct by default. The `semanticIndexOffset` can be used to /// offset the semantic indexes of each delegate so that the indexes are /// monotonically increasing. For example, if a scroll view contains two /// delegates where the first has 10 children contributing semantics, then the /// second delegate should offset its children by 10. /// /// {@tool snippet} /// /// This sample code shows how to use `semanticIndexOffset` to handle multiple /// delegates in a single scroll view. /// /// ```dart /// CustomScrollView( /// semanticChildCount: 4, /// slivers: <Widget>[ /// SliverGrid( /// gridDelegate: _gridDelegate, /// delegate: SliverChildBuilderDelegate( /// (BuildContext context, int index) { /// return const Text('...'); /// }, /// childCount: 2, /// ), /// ), /// SliverGrid( /// gridDelegate: _gridDelegate, /// delegate: SliverChildBuilderDelegate( /// (BuildContext context, int index) { /// return const Text('...'); /// }, /// childCount: 2, /// semanticIndexOffset: 2, /// ), /// ), /// ], /// ) /// ``` /// {@end-tool} /// /// In certain cases, only a subset of child widgets should be annotated /// with a semantic index. For example, in [new ListView.separated()] the /// separators do not have an index associated with them. This is done by /// providing a `semanticIndexCallback` which returns null for separators /// indexes and rounds the non-separator indexes down by half. /// /// {@tool snippet} /// /// This sample code shows how to use `semanticIndexCallback` to handle /// annotating a subset of child nodes with a semantic index. There is /// a [Spacer] widget at odd indexes which should not have a semantic /// index. /// /// ```dart /// CustomScrollView( /// semanticChildCount: 5, /// slivers: <Widget>[ /// SliverGrid( /// gridDelegate: _gridDelegate, /// delegate: SliverChildBuilderDelegate( /// (BuildContext context, int index) { /// if (index.isEven) { /// return const Text('...'); /// } /// return const Spacer(); /// }, /// semanticIndexCallback: (Widget widget, int localIndex) { /// if (localIndex.isEven) { /// return localIndex ~/ 2; /// } /// return null; /// }, /// childCount: 10, /// ), /// ), /// ], /// ) /// ``` /// {@end-tool} /// /// See also: /// /// * [SliverChildListDelegate], which is a delegate that has an explicit list /// of children. /// * [IndexedSemantics], for an example of manually annotating child nodes /// with semantic indexes. class SliverChildBuilderDelegate extends SliverChildDelegate { /// Creates a delegate that supplies children for slivers using the given /// builder callback. /// /// The [builder], [addAutomaticKeepAlives], [addRepaintBoundaries], /// [addSemanticIndexes], and [semanticIndexCallback] arguments must not be /// null. /// /// If the order in which [builder] returns children ever changes, consider /// providing a [findChildIndexCallback]. This allows the delegate to find the /// new index for a child that was previously located at a different index to /// attach the existing state to the [Widget] at its new location. const SliverChildBuilderDelegate( this.builder, { this.findChildIndexCallback, this.childCount, this.addAutomaticKeepAlives = true, this.addRepaintBoundaries = true, this.addSemanticIndexes = true, this.semanticIndexCallback = _kDefaultSemanticIndexCallback, this.semanticIndexOffset = 0, }) : assert(builder != null), assert(addAutomaticKeepAlives != null), assert(addRepaintBoundaries != null), assert(addSemanticIndexes != null), assert(semanticIndexCallback != null); /// Called to build children for the sliver. /// /// Will be called only for indices greater than or equal to zero and less /// than [childCount] (if [childCount] is non-null). /// /// Should return null if asked to build a widget with a greater index than /// exists. /// /// The delegate wraps the children returned by this builder in /// [RepaintBoundary] widgets. final NullableIndexedWidgetBuilder builder; /// The total number of children this delegate can provide. /// /// If null, the number of children is determined by the least index for which /// [builder] returns null. final int? childCount; /// Whether to wrap each child in an [AutomaticKeepAlive]. /// /// Typically, children in lazy list are wrapped in [AutomaticKeepAlive] /// widgets so that children can use [KeepAliveNotification]s to preserve /// their state when they would otherwise be garbage collected off-screen. /// /// This feature (and [addRepaintBoundaries]) must be disabled if the children /// are going to manually maintain their [KeepAlive] state. It may also be /// more efficient to disable this feature if it is known ahead of time that /// none of the children will ever try to keep themselves alive. /// /// Defaults to true. final bool addAutomaticKeepAlives; /// Whether to wrap each child in a [RepaintBoundary]. /// /// Typically, children in a scrolling container are wrapped in repaint /// boundaries so that they do not need to be repainted as the list scrolls. /// If the children are easy to repaint (e.g., solid color blocks or a short /// snippet of text), it might be more efficient to not add a repaint boundary /// and simply repaint the children during scrolling. /// /// Defaults to true. final bool addRepaintBoundaries; /// Whether to wrap each child in an [IndexedSemantics]. /// /// Typically, children in a scrolling container must be annotated with a /// semantic index in order to generate the correct accessibility /// announcements. This should only be set to false if the indexes have /// already been provided by an [IndexedSemantics] widget. /// /// Defaults to true. /// /// See also: /// /// * [IndexedSemantics], for an explanation of how to manually /// provide semantic indexes. final bool addSemanticIndexes; /// An initial offset to add to the semantic indexes generated by this widget. /// /// Defaults to zero. final int semanticIndexOffset; /// A [SemanticIndexCallback] which is used when [addSemanticIndexes] is true. /// /// Defaults to providing an index for each widget. final SemanticIndexCallback semanticIndexCallback; /// {@template flutter.widgets.SliverChildBuilderDelegate.findChildIndexCallback} /// Called to find the new index of a child based on its key in case of reordering. /// /// If not provided, a child widget may not map to its existing [RenderObject] /// when the order of children returned from the children builder changes. /// This may result in state-loss. /// /// This callback should take an input [Key], and it should return the /// index of the child element with that associated key, or null if not found. /// {@endtemplate} final ChildIndexGetter? findChildIndexCallback; @override int? findIndexByKey(Key key) { if (findChildIndexCallback == null) return null; assert(key != null); final Key childKey; if (key is _SaltedValueKey) { final _SaltedValueKey saltedValueKey = key; childKey = saltedValueKey.value; } else { childKey = key; } return findChildIndexCallback!(childKey); } @override @pragma('vm:notify-debugger-on-exception') Widget? build(BuildContext context, int index) { assert(builder != null); if (index < 0 || (childCount != null && index >= childCount!)) return null; Widget? child; try { child = builder(context, index); } catch (exception, stackTrace) { child = _createErrorWidget(exception, stackTrace); } if (child == null) { return null; } final Key? key = child.key != null ? _SaltedValueKey(child.key!) : null; if (addRepaintBoundaries) child = RepaintBoundary(child: child); if (addSemanticIndexes) { final int? semanticIndex = semanticIndexCallback(child, index); if (semanticIndex != null) child = IndexedSemantics(index: semanticIndex + semanticIndexOffset, child: child); } if (addAutomaticKeepAlives) child = AutomaticKeepAlive(child: child); return KeyedSubtree(key: key, child: child); } @override int? get estimatedChildCount => childCount; @override bool shouldRebuild(covariant SliverChildBuilderDelegate oldDelegate) => true; } /// A delegate that supplies children for slivers using an explicit list. /// /// Many slivers lazily construct their box children to avoid creating more /// children than are visible through the [Viewport]. This delegate provides /// children using an explicit list, which is convenient but reduces the benefit /// of building children lazily. /// /// In general building all the widgets in advance is not efficient. It is /// better to create a delegate that builds them on demand using /// [SliverChildBuilderDelegate] or by subclassing [SliverChildDelegate] /// directly. /// /// This class is provided for the cases where either the list of children is /// known well in advance (ideally the children are themselves compile-time /// constants, for example), and therefore will not be built each time the /// delegate itself is created, or the list is small, such that it's likely /// always visible (and thus there is nothing to be gained by building it on /// demand). For example, the body of a dialog box might fit both of these /// conditions. /// /// The widgets in the given [children] list are automatically wrapped in /// [AutomaticKeepAlive] widgets if [addAutomaticKeepAlives] is true (the /// default) and in [RepaintBoundary] widgets if [addRepaintBoundaries] is true /// (also the default). /// /// ## Accessibility /// /// The [CustomScrollView] requires that its semantic children are annotated /// using [IndexedSemantics]. This is done by default in the delegate with /// the `addSemanticIndexes` parameter set to true. /// /// If multiple delegates are used in a single scroll view, then the indexes /// will not be correct by default. The `semanticIndexOffset` can be used to /// offset the semantic indexes of each delegate so that the indexes are /// monotonically increasing. For example, if a scroll view contains two /// delegates where the first has 10 children contributing semantics, then the /// second delegate should offset its children by 10. /// /// In certain cases, only a subset of child widgets should be annotated /// with a semantic index. For example, in [new ListView.separated()] the /// separators do not have an index associated with them. This is done by /// providing a `semanticIndexCallback` which returns null for separators /// indexes and rounds the non-separator indexes down by half. /// /// See [SliverChildBuilderDelegate] for sample code using /// `semanticIndexOffset` and `semanticIndexCallback`. /// /// See also: /// /// * [SliverChildBuilderDelegate], which is a delegate that uses a builder /// callback to construct the children. class SliverChildListDelegate extends SliverChildDelegate { /// Creates a delegate that supplies children for slivers using the given /// list. /// /// The [children], [addAutomaticKeepAlives], [addRepaintBoundaries], /// [addSemanticIndexes], and [semanticIndexCallback] arguments must not be /// null. /// /// If the order of children` never changes, consider using the constant /// [SliverChildListDelegate.fixed] constructor. SliverChildListDelegate( this.children, { this.addAutomaticKeepAlives = true, this.addRepaintBoundaries = true, this.addSemanticIndexes = true, this.semanticIndexCallback = _kDefaultSemanticIndexCallback, this.semanticIndexOffset = 0, }) : assert(children != null), assert(addAutomaticKeepAlives != null), assert(addRepaintBoundaries != null), assert(addSemanticIndexes != null), assert(semanticIndexCallback != null), _keyToIndex = <Key?, int>{null: 0}; /// Creates a constant version of the delegate that supplies children for /// slivers using the given list. /// /// If the order of the children will change, consider using the regular /// [SliverChildListDelegate] constructor. /// /// The [children], [addAutomaticKeepAlives], [addRepaintBoundaries], /// [addSemanticIndexes], and [semanticIndexCallback] arguments must not be /// null. const SliverChildListDelegate.fixed( this.children, { this.addAutomaticKeepAlives = true, this.addRepaintBoundaries = true, this.addSemanticIndexes = true, this.semanticIndexCallback = _kDefaultSemanticIndexCallback, this.semanticIndexOffset = 0, }) : assert(children != null), assert(addAutomaticKeepAlives != null), assert(addRepaintBoundaries != null), assert(addSemanticIndexes != null), assert(semanticIndexCallback != null), _keyToIndex = null; /// Whether to wrap each child in an [AutomaticKeepAlive]. /// /// Typically, children in lazy list are wrapped in [AutomaticKeepAlive] /// widgets so that children can use [KeepAliveNotification]s to preserve /// their state when they would otherwise be garbage collected off-screen. /// /// This feature (and [addRepaintBoundaries]) must be disabled if the children /// are going to manually maintain their [KeepAlive] state. It may also be /// more efficient to disable this feature if it is known ahead of time that /// none of the children will ever try to keep themselves alive. /// /// Defaults to true. final bool addAutomaticKeepAlives; /// Whether to wrap each child in a [RepaintBoundary]. /// /// Typically, children in a scrolling container are wrapped in repaint /// boundaries so that they do not need to be repainted as the list scrolls. /// If the children are easy to repaint (e.g., solid color blocks or a short /// snippet of text), it might be more efficient to not add a repaint boundary /// and simply repaint the children during scrolling. /// /// Defaults to true. final bool addRepaintBoundaries; /// Whether to wrap each child in an [IndexedSemantics]. /// /// Typically, children in a scrolling container must be annotated with a /// semantic index in order to generate the correct accessibility /// announcements. This should only be set to false if the indexes have /// already been provided by an [IndexedSemantics] widget. /// /// Defaults to true. /// /// See also: /// /// * [IndexedSemantics], for an explanation of how to manually /// provide semantic indexes. final bool addSemanticIndexes; /// An initial offset to add to the semantic indexes generated by this widget. /// /// Defaults to zero. final int semanticIndexOffset; /// A [SemanticIndexCallback] which is used when [addSemanticIndexes] is true. /// /// Defaults to providing an index for each widget. final SemanticIndexCallback semanticIndexCallback; /// The widgets to display. /// /// If this list is going to be mutated, it is usually wise to put a [Key] on /// each of the child widgets, so that the framework can match old /// configurations to new configurations and maintain the underlying render /// objects. /// /// Also, a [Widget] in Flutter is immutable, so directly modifying the /// [children] such as `someWidget.children.add(...)` or /// passing a reference of the original list value to the [children] parameter /// will result in incorrect behaviors. Whenever the /// children list is modified, a new list object should be provided. /// /// The following code corrects the problem mentioned above. /// /// ```dart /// class SomeWidgetState extends State<SomeWidget> { /// List<Widget> _children; /// /// void initState() { /// _children = []; /// } /// /// void someHandler() { /// setState(() { /// // The key here allows Flutter to reuse the underlying render /// // objects even if the children list is recreated. /// _children.add(ChildWidget(key: UniqueKey())); /// }); /// } /// /// Widget build(BuildContext context) { /// // Always create a new list of children as a Widget is immutable. /// return PageView(children: List<Widget>.of(_children)); /// } /// } /// ``` final List<Widget> children; /// A map to cache key to index lookup for children. /// /// _keyToIndex[null] is used as current index during the lazy loading process /// in [_findChildIndex]. _keyToIndex should never be used for looking up null key. final Map<Key?, int>? _keyToIndex; bool get _isConstantInstance => _keyToIndex == null; int? _findChildIndex(Key key) { if (_isConstantInstance) { return null; } // Lazily fill the [_keyToIndex]. if (!_keyToIndex!.containsKey(key)) { int index = _keyToIndex![null]!; while (index < children.length) { final Widget child = children[index]; if (child.key != null) { _keyToIndex![child.key] = index; } if (child.key == key) { // Record current index for next function call. _keyToIndex![null] = index + 1; return index; } index += 1; } _keyToIndex![null] = index; } else { return _keyToIndex![key]; } return null; } @override int? findIndexByKey(Key key) { assert(key != null); final Key childKey; if (key is _SaltedValueKey) { final _SaltedValueKey saltedValueKey = key; childKey = saltedValueKey.value; } else { childKey = key; } return _findChildIndex(childKey); } @override Widget? build(BuildContext context, int index) { assert(children != null); if (index < 0 || index >= children.length) return null; Widget child = children[index]; final Key? key = child.key != null? _SaltedValueKey(child.key!) : null; assert( child != null, "The sliver's children must not contain null values, but a null value was found at index $index", ); if (addRepaintBoundaries) child = RepaintBoundary(child: child); if (addSemanticIndexes) { final int? semanticIndex = semanticIndexCallback(child, index); if (semanticIndex != null) child = IndexedSemantics(index: semanticIndex + semanticIndexOffset, child: child); } if (addAutomaticKeepAlives) child = AutomaticKeepAlive(child: child); return KeyedSubtree(key: key, child: child); } @override int? get estimatedChildCount => children.length; @override bool shouldRebuild(covariant SliverChildListDelegate oldDelegate) { return children != oldDelegate.children; } } /// A base class for sliver that have [KeepAlive] children. /// /// See also: /// /// * [KeepAlive], which marks whether its child widget should be kept alive. /// * [SliverChildBuilderDelegate] and [SliverChildListDelegate], slivers /// which make use of the keep alive functionality through the /// `addAutomaticKeepAlives` property. /// * [SliverGrid] and [SliverList], two sliver widgets that are commonly /// wrapped with [KeepAlive] widgets to preserve their sliver child subtrees. abstract class SliverWithKeepAliveWidget extends RenderObjectWidget { /// Initializes fields for subclasses. const SliverWithKeepAliveWidget({ super.key, }); @override RenderSliverWithKeepAliveMixin createRenderObject(BuildContext context); } /// A base class for sliver that have multiple box children. /// /// Helps subclasses build their children lazily using a [SliverChildDelegate]. /// /// The widgets returned by the [delegate] are cached and the delegate is only /// consulted again if it changes and the new delegate's /// [SliverChildDelegate.shouldRebuild] method returns true. abstract class SliverMultiBoxAdaptorWidget extends SliverWithKeepAliveWidget { /// Initializes fields for subclasses. const SliverMultiBoxAdaptorWidget({ super.key, required this.delegate, }) : assert(delegate != null); /// {@template flutter.widgets.SliverMultiBoxAdaptorWidget.delegate} /// The delegate that provides the children for this widget. /// /// The children are constructed lazily using this delegate to avoid creating /// more children than are visible through the [Viewport]. /// /// ## Using more than one delegate in a [Viewport] /// /// If multiple delegates are used in a single scroll view, the first child of /// each delegate will always be laid out, even if it extends beyond the /// currently viewable area. This is because at least one child is required in /// order to estimate the max scroll offset for the whole scroll view, as it /// uses the currently built children to estimate the remaining children's /// extent. /// /// See also: /// /// * [SliverChildBuilderDelegate] and [SliverChildListDelegate], which are /// commonly used subclasses of [SliverChildDelegate] that use a builder /// callback and an explicit child list, respectively. /// {@endtemplate} final SliverChildDelegate delegate; @override SliverMultiBoxAdaptorElement createElement() => SliverMultiBoxAdaptorElement(this); @override RenderSliverMultiBoxAdaptor createRenderObject(BuildContext context); /// Returns an estimate of the max scroll extent for all the children. /// /// Subclasses should override this function if they have additional /// information about their max scroll extent. /// /// This is used by [SliverMultiBoxAdaptorElement] to implement part of the /// [RenderSliverBoxChildManager] API. /// /// The default implementation defers to [delegate] via its /// [SliverChildDelegate.estimateMaxScrollOffset] method. double? estimateMaxScrollOffset( SliverConstraints? constraints, int firstIndex, int lastIndex, double leadingScrollOffset, double trailingScrollOffset, ) { assert(lastIndex >= firstIndex); return delegate.estimateMaxScrollOffset( firstIndex, lastIndex, leadingScrollOffset, trailingScrollOffset, ); } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(DiagnosticsProperty<SliverChildDelegate>('delegate', delegate)); } } /// A sliver that places multiple box children in a linear array along the main /// axis. /// /// Each child is forced to have the [SliverConstraints.crossAxisExtent] in the /// cross axis but determines its own main axis extent. /// /// [SliverList] determines its scroll offset by "dead reckoning" because /// children outside the visible part of the sliver are not materialized, which /// means [SliverList] cannot learn their main axis extent. Instead, newly /// materialized children are placed adjacent to existing children. /// /// {@youtube 560 315 https://www.youtube.com/watch?v=ORiTTaVY6mM} /// /// If the children have a fixed extent in the main axis, consider using /// [SliverFixedExtentList] rather than [SliverList] because /// [SliverFixedExtentList] does not need to perform layout on its children to /// obtain their extent in the main axis and is therefore more efficient. /// /// {@macro flutter.widgets.SliverChildDelegate.lifecycle} /// /// See also: /// /// * <https://flutter.dev/docs/development/ui/advanced/slivers>, a description /// of what slivers are and how to use them. /// * [SliverFixedExtentList], which is more efficient for children with /// the same extent in the main axis. /// * [SliverPrototypeExtentList], which is similar to [SliverFixedExtentList] /// except that it uses a prototype list item instead of a pixel value to define /// the main axis extent of each item. /// * [SliverGrid], which places its children in arbitrary positions. class SliverList extends SliverMultiBoxAdaptorWidget { /// Creates a sliver that places box children in a linear array. const SliverList({ super.key, required super.delegate, }); @override SliverMultiBoxAdaptorElement createElement() => SliverMultiBoxAdaptorElement(this, replaceMovedChildren: true); @override RenderSliverList createRenderObject(BuildContext context) { final SliverMultiBoxAdaptorElement element = context as SliverMultiBoxAdaptorElement; return RenderSliverList(childManager: element); } } /// A sliver that places multiple box children with the same main axis extent in /// a linear array. /// /// [SliverFixedExtentList] places its children in a linear array along the main /// axis starting at offset zero and without gaps. Each child is forced to have /// the [itemExtent] in the main axis and the /// [SliverConstraints.crossAxisExtent] in the cross axis. /// /// [SliverFixedExtentList] is more efficient than [SliverList] because /// [SliverFixedExtentList] does not need to perform layout on its children to /// obtain their extent in the main axis. /// /// {@tool snippet} /// /// This example, which would be inserted into a [CustomScrollView.slivers] /// list, shows an infinite number of items in varying shades of blue: /// /// ```dart /// SliverFixedExtentList( /// itemExtent: 50.0, /// delegate: SliverChildBuilderDelegate( /// (BuildContext context, int index) { /// return Container( /// alignment: Alignment.center, /// color: Colors.lightBlue[100 * (index % 9)], /// child: Text('list item $index'), /// ); /// }, /// ), /// ) /// ``` /// {@end-tool} /// /// {@macro flutter.widgets.SliverChildDelegate.lifecycle} /// /// See also: /// /// * [SliverPrototypeExtentList], which is similar to [SliverFixedExtentList] /// except that it uses a prototype list item instead of a pixel value to define /// the main axis extent of each item. /// * [SliverFillViewport], which determines the [itemExtent] based on /// [SliverConstraints.viewportMainAxisExtent]. /// * [SliverList], which does not require its children to have the same /// extent in the main axis. class SliverFixedExtentList extends SliverMultiBoxAdaptorWidget { /// Creates a sliver that places box children with the same main axis extent /// in a linear array. const SliverFixedExtentList({ super.key, required super.delegate, required this.itemExtent, }); /// The extent the children are forced to have in the main axis. final double itemExtent; @override RenderSliverFixedExtentList createRenderObject(BuildContext context) { final SliverMultiBoxAdaptorElement element = context as SliverMultiBoxAdaptorElement; return RenderSliverFixedExtentList(childManager: element, itemExtent: itemExtent); } @override void updateRenderObject(BuildContext context, RenderSliverFixedExtentList renderObject) { renderObject.itemExtent = itemExtent; } } /// A sliver that places multiple box children in a two dimensional arrangement. /// /// [SliverGrid] places its children in arbitrary positions determined by /// [gridDelegate]. Each child is forced to have the size specified by the /// [gridDelegate]. /// /// The main axis direction of a grid is the direction in which it scrolls; the /// cross axis direction is the orthogonal direction. /// /// {@youtube 560 315 https://www.youtube.com/watch?v=ORiTTaVY6mM} /// /// {@tool snippet} /// /// This example, which would be inserted into a [CustomScrollView.slivers] /// list, shows twenty boxes in a pretty teal grid: /// /// ```dart /// SliverGrid( /// gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( /// maxCrossAxisExtent: 200.0, /// mainAxisSpacing: 10.0, /// crossAxisSpacing: 10.0, /// childAspectRatio: 4.0, /// ), /// delegate: SliverChildBuilderDelegate( /// (BuildContext context, int index) { /// return Container( /// alignment: Alignment.center, /// color: Colors.teal[100 * (index % 9)], /// child: Text('grid item $index'), /// ); /// }, /// childCount: 20, /// ), /// ) /// ``` /// {@end-tool} /// /// {@macro flutter.widgets.SliverChildDelegate.lifecycle} /// /// See also: /// /// * [SliverList], which places its children in a linear array. /// * [SliverFixedExtentList], which places its children in a linear /// array with a fixed extent in the main axis. /// * [SliverPrototypeExtentList], which is similar to [SliverFixedExtentList] /// except that it uses a prototype list item instead of a pixel value to define /// the main axis extent of each item. class SliverGrid extends SliverMultiBoxAdaptorWidget { /// Creates a sliver that places multiple box children in a two dimensional /// arrangement. const SliverGrid({ super.key, required super.delegate, required this.gridDelegate, }); /// Creates a sliver that places multiple box children in a two dimensional /// arrangement with a fixed number of tiles in the cross axis. /// /// Uses a [SliverGridDelegateWithFixedCrossAxisCount] as the [gridDelegate], /// and a [SliverChildListDelegate] as the [delegate]. /// /// See also: /// /// * [GridView.count], the equivalent constructor for [GridView] widgets. SliverGrid.count({ super.key, required int crossAxisCount, double mainAxisSpacing = 0.0, double crossAxisSpacing = 0.0, double childAspectRatio = 1.0, List<Widget> children = const <Widget>[], }) : gridDelegate = SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: crossAxisCount, mainAxisSpacing: mainAxisSpacing, crossAxisSpacing: crossAxisSpacing, childAspectRatio: childAspectRatio, ), super(delegate: SliverChildListDelegate(children)); /// Creates a sliver that places multiple box children in a two dimensional /// arrangement with tiles that each have a maximum cross-axis extent. /// /// Uses a [SliverGridDelegateWithMaxCrossAxisExtent] as the [gridDelegate], /// and a [SliverChildListDelegate] as the [delegate]. /// /// See also: /// /// * [GridView.extent], the equivalent constructor for [GridView] widgets. SliverGrid.extent({ super.key, required double maxCrossAxisExtent, double mainAxisSpacing = 0.0, double crossAxisSpacing = 0.0, double childAspectRatio = 1.0, List<Widget> children = const <Widget>[], }) : gridDelegate = SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: maxCrossAxisExtent, mainAxisSpacing: mainAxisSpacing, crossAxisSpacing: crossAxisSpacing, childAspectRatio: childAspectRatio, ), super(delegate: SliverChildListDelegate(children)); /// The delegate that controls the size and position of the children. final SliverGridDelegate gridDelegate; @override RenderSliverGrid createRenderObject(BuildContext context) { final SliverMultiBoxAdaptorElement element = context as SliverMultiBoxAdaptorElement; return RenderSliverGrid(childManager: element, gridDelegate: gridDelegate); } @override void updateRenderObject(BuildContext context, RenderSliverGrid renderObject) { renderObject.gridDelegate = gridDelegate; } @override double estimateMaxScrollOffset( SliverConstraints? constraints, int firstIndex, int lastIndex, double leadingScrollOffset, double trailingScrollOffset, ) { return super.estimateMaxScrollOffset( constraints, firstIndex, lastIndex, leadingScrollOffset, trailingScrollOffset, ) ?? gridDelegate.getLayout(constraints!).computeMaxScrollOffset(delegate.estimatedChildCount!); } } /// An element that lazily builds children for a [SliverMultiBoxAdaptorWidget]. /// /// Implements [RenderSliverBoxChildManager], which lets this element manage /// the children of subclasses of [RenderSliverMultiBoxAdaptor]. class SliverMultiBoxAdaptorElement extends RenderObjectElement implements RenderSliverBoxChildManager { /// Creates an element that lazily builds children for the given widget. /// /// If `replaceMovedChildren` is set to true, a new child is proactively /// inflate for the index that was previously occupied by a child that moved /// to a new index. The layout offset of the moved child is copied over to the /// new child. RenderObjects, that depend on the layout offset of existing /// children during [RenderObject.performLayout] should set this to true /// (example: [RenderSliverList]). For RenderObjects that figure out the /// layout offset of their children without looking at the layout offset of /// existing children this should be set to false (example: /// [RenderSliverFixedExtentList]) to avoid inflating unnecessary children. SliverMultiBoxAdaptorElement(SliverMultiBoxAdaptorWidget super.widget, {bool replaceMovedChildren = false}) : _replaceMovedChildren = replaceMovedChildren; final bool _replaceMovedChildren; @override RenderSliverMultiBoxAdaptor get renderObject => super.renderObject as RenderSliverMultiBoxAdaptor; @override void update(covariant SliverMultiBoxAdaptorWidget newWidget) { final SliverMultiBoxAdaptorWidget oldWidget = widget as SliverMultiBoxAdaptorWidget; super.update(newWidget); final SliverChildDelegate newDelegate = newWidget.delegate; final SliverChildDelegate oldDelegate = oldWidget.delegate; if (newDelegate != oldDelegate && (newDelegate.runtimeType != oldDelegate.runtimeType || newDelegate.shouldRebuild(oldDelegate))) performRebuild(); } final SplayTreeMap<int, Element?> _childElements = SplayTreeMap<int, Element?>(); RenderBox? _currentBeforeChild; @override void performRebuild() { super.performRebuild(); _currentBeforeChild = null; bool childrenUpdated = false; assert(_currentlyUpdatingChildIndex == null); try { final SplayTreeMap<int, Element?> newChildren = SplayTreeMap<int, Element?>(); final Map<int, double> indexToLayoutOffset = HashMap<int, double>(); final SliverMultiBoxAdaptorWidget adaptorWidget = widget as SliverMultiBoxAdaptorWidget; void processElement(int index) { _currentlyUpdatingChildIndex = index; if (_childElements[index] != null && _childElements[index] != newChildren[index]) { // This index has an old child that isn't used anywhere and should be deactivated. _childElements[index] = updateChild(_childElements[index], null, index); childrenUpdated = true; } final Element? newChild = updateChild(newChildren[index], _build(index, adaptorWidget), index); if (newChild != null) { childrenUpdated = childrenUpdated || _childElements[index] != newChild; _childElements[index] = newChild; final SliverMultiBoxAdaptorParentData parentData = newChild.renderObject!.parentData! as SliverMultiBoxAdaptorParentData; if (index == 0) { parentData.layoutOffset = 0.0; } else if (indexToLayoutOffset.containsKey(index)) { parentData.layoutOffset = indexToLayoutOffset[index]; } if (!parentData.keptAlive) _currentBeforeChild = newChild.renderObject as RenderBox?; } else { childrenUpdated = true; _childElements.remove(index); } } for (final int index in _childElements.keys.toList()) { final Key? key = _childElements[index]!.widget.key; final int? newIndex = key == null ? null : adaptorWidget.delegate.findIndexByKey(key); final SliverMultiBoxAdaptorParentData? childParentData = _childElements[index]!.renderObject?.parentData as SliverMultiBoxAdaptorParentData?; if (childParentData != null && childParentData.layoutOffset != null) indexToLayoutOffset[index] = childParentData.layoutOffset!; if (newIndex != null && newIndex != index) { // The layout offset of the child being moved is no longer accurate. if (childParentData != null) childParentData.layoutOffset = null; newChildren[newIndex] = _childElements[index]; if (_replaceMovedChildren) { // We need to make sure the original index gets processed. newChildren.putIfAbsent(index, () => null); } // We do not want the remapped child to get deactivated during processElement. _childElements.remove(index); } else { newChildren.putIfAbsent(index, () => _childElements[index]); } } renderObject.debugChildIntegrityEnabled = false; // Moving children will temporary violate the integrity. newChildren.keys.forEach(processElement); // An element rebuild only updates existing children. The underflow check // is here to make sure we look ahead one more child if we were at the end // of the child list before the update. By doing so, we can update the max // scroll offset during the layout phase. Otherwise, the layout phase may // be skipped, and the scroll view may be stuck at the previous max // scroll offset. // // This logic is not needed if any existing children has been updated, // because we will not skip the layout phase if that happens. if (!childrenUpdated && _didUnderflow) { final int lastKey = _childElements.lastKey() ?? -1; final int rightBoundary = lastKey + 1; newChildren[rightBoundary] = _childElements[rightBoundary]; processElement(rightBoundary); } } finally { _currentlyUpdatingChildIndex = null; renderObject.debugChildIntegrityEnabled = true; } } Widget? _build(int index, SliverMultiBoxAdaptorWidget widget) { return widget.delegate.build(this, index); } @override void createChild(int index, { required RenderBox? after }) { assert(_currentlyUpdatingChildIndex == null); owner!.buildScope(this, () { final bool insertFirst = after == null; assert(insertFirst || _childElements[index-1] != null); _currentBeforeChild = insertFirst ? null : (_childElements[index-1]!.renderObject as RenderBox?); Element? newChild; try { final SliverMultiBoxAdaptorWidget adaptorWidget = widget as SliverMultiBoxAdaptorWidget; _currentlyUpdatingChildIndex = index; newChild = updateChild(_childElements[index], _build(index, adaptorWidget), index); } finally { _currentlyUpdatingChildIndex = null; } if (newChild != null) { _childElements[index] = newChild; } else { _childElements.remove(index); } }); } @override Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) { final SliverMultiBoxAdaptorParentData? oldParentData = child?.renderObject?.parentData as SliverMultiBoxAdaptorParentData?; final Element? newChild = super.updateChild(child, newWidget, newSlot); final SliverMultiBoxAdaptorParentData? newParentData = newChild?.renderObject?.parentData as SliverMultiBoxAdaptorParentData?; // Preserve the old layoutOffset if the renderObject was swapped out. if (oldParentData != newParentData && oldParentData != null && newParentData != null) { newParentData.layoutOffset = oldParentData.layoutOffset; } return newChild; } @override void forgetChild(Element child) { assert(child != null); assert(child.slot != null); assert(_childElements.containsKey(child.slot)); _childElements.remove(child.slot); super.forgetChild(child); } @override void removeChild(RenderBox child) { final int index = renderObject.indexOf(child); assert(_currentlyUpdatingChildIndex == null); assert(index >= 0); owner!.buildScope(this, () { assert(_childElements.containsKey(index)); try { _currentlyUpdatingChildIndex = index; final Element? result = updateChild(_childElements[index], null, index); assert(result == null); } finally { _currentlyUpdatingChildIndex = null; } _childElements.remove(index); assert(!_childElements.containsKey(index)); }); } static double _extrapolateMaxScrollOffset( int firstIndex, int lastIndex, double leadingScrollOffset, double trailingScrollOffset, int childCount, ) { if (lastIndex == childCount - 1) return trailingScrollOffset; final int reifiedCount = lastIndex - firstIndex + 1; final double averageExtent = (trailingScrollOffset - leadingScrollOffset) / reifiedCount; final int remainingCount = childCount - lastIndex - 1; return trailingScrollOffset + averageExtent * remainingCount; } @override double estimateMaxScrollOffset( SliverConstraints? constraints, { int? firstIndex, int? lastIndex, double? leadingScrollOffset, double? trailingScrollOffset, }) { final int? childCount = estimatedChildCount; if (childCount == null) return double.infinity; return (widget as SliverMultiBoxAdaptorWidget).estimateMaxScrollOffset( constraints, firstIndex!, lastIndex!, leadingScrollOffset!, trailingScrollOffset!, ) ?? _extrapolateMaxScrollOffset( firstIndex, lastIndex, leadingScrollOffset, trailingScrollOffset, childCount, ); } /// The best available estimate of [childCount], or null if no estimate is available. /// /// This differs from [childCount] in that [childCount] never returns null (and must /// not be accessed if the child count is not yet available, meaning the [createChild] /// method has not been provided an index that does not create a child). /// /// See also: /// /// * [SliverChildDelegate.estimatedChildCount], to which this getter defers. int? get estimatedChildCount => (widget as SliverMultiBoxAdaptorWidget).delegate.estimatedChildCount; @override int get childCount { int? result = estimatedChildCount; if (result == null) { // Since childCount was called, we know that we reached the end of // the list (as in, _build return null once), so we know that the // list is finite. // Let's do an open-ended binary search to find the end of the list // manually. int lo = 0; int hi = 1; final SliverMultiBoxAdaptorWidget adaptorWidget = widget as SliverMultiBoxAdaptorWidget; const int max = kIsWeb ? 9007199254740992 // max safe integer on JS (from 0 to this number x != x+1) : ((1 << 63) - 1); while (_build(hi - 1, adaptorWidget) != null) { lo = hi - 1; if (hi < max ~/ 2) { hi *= 2; } else if (hi < max) { hi = max; } else { throw FlutterError( 'Could not find the number of children in ${adaptorWidget.delegate}.\n' "The childCount getter was called (implying that the delegate's builder returned null " 'for a positive index), but even building the child with index $hi (the maximum ' 'possible integer) did not return null. Consider implementing childCount to avoid ' 'the cost of searching for the final child.', ); } } while (hi - lo > 1) { final int mid = (hi - lo) ~/ 2 + lo; if (_build(mid - 1, adaptorWidget) == null) { hi = mid; } else { lo = mid; } } result = lo; } return result; } @override void didStartLayout() { assert(debugAssertChildListLocked()); } @override void didFinishLayout() { assert(debugAssertChildListLocked()); final int firstIndex = _childElements.firstKey() ?? 0; final int lastIndex = _childElements.lastKey() ?? 0; (widget as SliverMultiBoxAdaptorWidget).delegate.didFinishLayout(firstIndex, lastIndex); } int? _currentlyUpdatingChildIndex; @override bool debugAssertChildListLocked() { assert(_currentlyUpdatingChildIndex == null); return true; } @override void didAdoptChild(RenderBox child) { assert(_currentlyUpdatingChildIndex != null); final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData; childParentData.index = _currentlyUpdatingChildIndex; } bool _didUnderflow = false; @override void setDidUnderflow(bool value) { _didUnderflow = value; } @override void insertRenderObjectChild(covariant RenderObject child, int slot) { assert(slot != null); assert(_currentlyUpdatingChildIndex == slot); assert(renderObject.debugValidateChild(child)); renderObject.insert(child as RenderBox, after: _currentBeforeChild); assert(() { final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData; assert(slot == childParentData.index); return true; }()); } @override void moveRenderObjectChild(covariant RenderObject child, int oldSlot, int newSlot) { assert(newSlot != null); assert(_currentlyUpdatingChildIndex == newSlot); renderObject.move(child as RenderBox, after: _currentBeforeChild); } @override void removeRenderObjectChild(covariant RenderObject child, int slot) { assert(_currentlyUpdatingChildIndex != null); renderObject.remove(child as RenderBox); } @override void visitChildren(ElementVisitor visitor) { // The toList() is to make a copy so that the underlying list can be modified by // the visitor: assert(!_childElements.values.any((Element? child) => child == null)); _childElements.values.cast<Element>().toList().forEach(visitor); } @override void debugVisitOnstageChildren(ElementVisitor visitor) { _childElements.values.cast<Element>().where((Element child) { final SliverMultiBoxAdaptorParentData parentData = child.renderObject!.parentData! as SliverMultiBoxAdaptorParentData; final double itemExtent; switch (renderObject.constraints.axis) { case Axis.horizontal: itemExtent = child.renderObject!.paintBounds.width; break; case Axis.vertical: itemExtent = child.renderObject!.paintBounds.height; break; } return parentData.layoutOffset != null && parentData.layoutOffset! < renderObject.constraints.scrollOffset + renderObject.constraints.remainingPaintExtent && parentData.layoutOffset! + itemExtent > renderObject.constraints.scrollOffset; }).forEach(visitor); } } /// A sliver widget that makes its sliver child partially transparent. /// /// This class paints its sliver child into an intermediate buffer and then /// blends the sliver back into the scene partially transparent. /// /// For values of opacity other than 0.0 and 1.0, this class is relatively /// expensive because it requires painting the sliver child into an intermediate /// buffer. For the value 0.0, the sliver child is simply not painted at all. /// For the value 1.0, the sliver child is painted immediately without an /// intermediate buffer. /// /// {@tool snippet} /// /// This example shows a [SliverList] when the `_visible` member field is true, /// and hides it when it is false: /// /// ```dart /// bool _visible = true; /// List<Widget> listItems = const <Widget>[ /// Text('Now you see me,'), /// Text("Now you don't!"), /// ]; /// /// SliverOpacity( /// opacity: _visible ? 1.0 : 0.0, /// sliver: SliverList( /// delegate: SliverChildListDelegate(listItems), /// ), /// ) /// ``` /// {@end-tool} /// /// This is more efficient than adding and removing the sliver child widget /// from the tree on demand. /// /// See also: /// /// * [Opacity], which can apply a uniform alpha effect to its child using the /// RenderBox layout protocol. /// * [AnimatedOpacity], which uses an animation internally to efficiently /// animate [Opacity]. class SliverOpacity extends SingleChildRenderObjectWidget { /// Creates a sliver that makes its sliver child partially transparent. /// /// The [opacity] argument must not be null and must be between 0.0 and 1.0 /// (inclusive). const SliverOpacity({ super.key, required this.opacity, this.alwaysIncludeSemantics = false, Widget? sliver, }) : assert(opacity != null && opacity >= 0.0 && opacity <= 1.0), assert(alwaysIncludeSemantics != null), super(child: sliver); /// The fraction to scale the sliver child's alpha value. /// /// An opacity of 1.0 is fully opaque. An opacity of 0.0 is fully transparent /// (i.e. invisible). /// /// The opacity must not be null. /// /// Values 1.0 and 0.0 are painted with a fast path. Other values /// require painting the sliver child into an intermediate buffer, which is /// expensive. final double opacity; /// Whether the semantic information of the sliver child is always included. /// /// Defaults to false. /// /// When true, regardless of the opacity settings, the sliver child semantic /// information is exposed as if the widget were fully visible. This is /// useful in cases where labels may be hidden during animations that /// would otherwise contribute relevant semantics. final bool alwaysIncludeSemantics; @override RenderSliverOpacity createRenderObject(BuildContext context) { return RenderSliverOpacity( opacity: opacity, alwaysIncludeSemantics: alwaysIncludeSemantics, ); } @override void updateRenderObject(BuildContext context, RenderSliverOpacity renderObject) { renderObject ..opacity = opacity ..alwaysIncludeSemantics = alwaysIncludeSemantics; } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(DiagnosticsProperty<double>('opacity', opacity)); properties.add(FlagProperty( 'alwaysIncludeSemantics', value: alwaysIncludeSemantics, ifTrue: 'alwaysIncludeSemantics', )); } } /// A sliver widget that is invisible during hit testing. /// /// When [ignoring] is true, this widget (and its subtree) is invisible /// to hit testing. It still consumes space during layout and paints its sliver /// child as usual. It just cannot be the target of located events, because it /// returns false from [RenderSliver.hitTest]. /// /// When [ignoringSemantics] is true, the subtree will be invisible to /// the semantics layer (and thus e.g. accessibility tools). If /// [ignoringSemantics] is null, it uses the value of [ignoring]. class SliverIgnorePointer extends SingleChildRenderObjectWidget { /// Creates a sliver widget that is invisible to hit testing. /// /// The [ignoring] argument must not be null. If [ignoringSemantics] is null, /// this render object will be ignored for semantics if [ignoring] is true. const SliverIgnorePointer({ super.key, this.ignoring = true, this.ignoringSemantics, Widget? sliver, }) : assert(ignoring != null), super(child: sliver); /// Whether this sliver is ignored during hit testing. /// /// Regardless of whether this sliver is ignored during hit testing, it will /// still consume space during layout and be visible during painting. final bool ignoring; /// Whether the semantics of this sliver is ignored when compiling the /// semantics tree. /// /// If null, defaults to value of [ignoring]. /// /// See [SemanticsNode] for additional information about the semantics tree. final bool? ignoringSemantics; @override RenderSliverIgnorePointer createRenderObject(BuildContext context) { return RenderSliverIgnorePointer( ignoring: ignoring, ignoringSemantics: ignoringSemantics, ); } @override void updateRenderObject(BuildContext context, RenderSliverIgnorePointer renderObject) { renderObject ..ignoring = ignoring ..ignoringSemantics = ignoringSemantics; } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(DiagnosticsProperty<bool>('ignoring', ignoring)); properties.add(DiagnosticsProperty<bool>('ignoringSemantics', ignoringSemantics, defaultValue: null)); } } /// A sliver that lays its sliver child out as if it was in the tree, but /// without painting anything, without making the sliver child available for hit /// testing, and without taking any room in the parent. /// /// Animations continue to run in offstage sliver children, and therefore use /// battery and CPU time, regardless of whether the animations end up being /// visible. /// /// To hide a sliver widget from view while it is /// not needed, prefer removing the widget from the tree entirely rather than /// keeping it alive in an [Offstage] subtree. class SliverOffstage extends SingleChildRenderObjectWidget { /// Creates a sliver that visually hides its sliver child. const SliverOffstage({ super.key, this.offstage = true, Widget? sliver, }) : assert(offstage != null), super(child: sliver); /// Whether the sliver child is hidden from the rest of the tree. /// /// If true, the sliver child is laid out as if it was in the tree, but /// without painting anything, without making the child available for hit /// testing, and without taking any room in the parent. /// /// If false, the sliver child is included in the tree as normal. final bool offstage; @override RenderSliverOffstage createRenderObject(BuildContext context) => RenderSliverOffstage(offstage: offstage); @override void updateRenderObject(BuildContext context, RenderSliverOffstage renderObject) { renderObject.offstage = offstage; } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(DiagnosticsProperty<bool>('offstage', offstage)); } @override SingleChildRenderObjectElement createElement() => _SliverOffstageElement(this); } class _SliverOffstageElement extends SingleChildRenderObjectElement { _SliverOffstageElement(SliverOffstage super.widget); @override void debugVisitOnstageChildren(ElementVisitor visitor) { if (!(widget as SliverOffstage).offstage) super.debugVisitOnstageChildren(visitor); } } /// Mark a child as needing to stay alive even when it's in a lazy list that /// would otherwise remove it. /// /// This widget is for use in [SliverWithKeepAliveWidget]s, such as /// [SliverGrid] or [SliverList]. /// /// This widget is rarely used directly. The [SliverChildBuilderDelegate] and /// [SliverChildListDelegate] delegates, used with [SliverList] and /// [SliverGrid], as well as the scroll view counterparts [ListView] and /// [GridView], have an `addAutomaticKeepAlives` feature, which is enabled by /// default, and which causes [AutomaticKeepAlive] widgets to be inserted around /// each child, causing [KeepAlive] widgets to be automatically added and /// configured in response to [KeepAliveNotification]s. /// /// Therefore, to keep a widget alive, it is more common to use those /// notifications than to directly deal with [KeepAlive] widgets. /// /// In practice, the simplest way to deal with these notifications is to mix /// [AutomaticKeepAliveClientMixin] into one's [State]. See the documentation /// for that mixin class for details. class KeepAlive extends ParentDataWidget<KeepAliveParentDataMixin> { /// Marks a child as needing to remain alive. /// /// The [child] and [keepAlive] arguments must not be null. const KeepAlive({ super.key, required this.keepAlive, required super.child, }) : assert(child != null), assert(keepAlive != null); /// Whether to keep the child alive. /// /// If this is false, it is as if this widget was omitted. final bool keepAlive; @override void applyParentData(RenderObject renderObject) { assert(renderObject.parentData is KeepAliveParentDataMixin); final KeepAliveParentDataMixin parentData = renderObject.parentData! as KeepAliveParentDataMixin; if (parentData.keepAlive != keepAlive) { parentData.keepAlive = keepAlive; final AbstractNode? targetParent = renderObject.parent; if (targetParent is RenderObject && !keepAlive) targetParent.markNeedsLayout(); // No need to redo layout if it became true. } } // We only return true if [keepAlive] is true, because turning _off_ keep // alive requires a layout to do the garbage collection (but turning it on // requires nothing, since by definition the widget is already alive and won't // go away _unless_ we do a layout). @override bool debugCanApplyOutOfTurn() => keepAlive; @override Type get debugTypicalAncestorWidgetClass => SliverWithKeepAliveWidget; @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(DiagnosticsProperty<bool>('keepAlive', keepAlive)); } } // Return a Widget for the given Exception Widget _createErrorWidget(Object exception, StackTrace stackTrace) { final FlutterErrorDetails details = FlutterErrorDetails( exception: exception, stack: stackTrace, library: 'widgets library', context: ErrorDescription('building'), ); FlutterError.reportError(details); return ErrorWidget.builder(details); }