| // 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 HashMap, SplayTreeMap; |
| import 'dart:math' as math; |
| |
| import 'package:flutter/foundation.dart'; |
| import 'package:flutter/rendering.dart'; |
| |
| import 'automatic_keep_alive.dart'; |
| import 'basic.dart'; |
| import 'framework.dart'; |
| import 'scroll_delegate.dart'; |
| |
| /// A base class for slivers 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 slivers 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, |
| }); |
| |
| /// {@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. |
| /// |
| /// _To learn more about slivers, see [CustomScrollView.slivers]._ |
| /// |
| /// 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. |
| /// * [SliverAnimatedList], which animates items added to or removed from a |
| /// list. |
| /// * [SliverGrid], which places multiple children in a two dimensional grid. |
| /// * [SliverAnimatedGrid], a sliver which animates items when they are |
| /// inserted into or removed from a grid. |
| class SliverList extends SliverMultiBoxAdaptorWidget { |
| /// Creates a sliver that places box children in a linear array. |
| const SliverList({ |
| super.key, |
| required super.delegate, |
| }); |
| |
| /// A sliver that places multiple box children in a linear array along the main |
| /// axis. |
| /// |
| /// This constructor is appropriate for sliver lists with a large (or |
| /// infinite) number of children because the builder is called only for those |
| /// children that are actually visible. |
| /// |
| /// Providing a non-null `itemCount` improves the ability of the [SliverGrid] |
| /// to estimate the maximum scroll extent. |
| /// |
| /// `itemBuilder` will be called only with indices greater than or equal to |
| /// zero and less than `itemCount`. |
| /// |
| /// {@macro flutter.widgets.ListView.builder.itemBuilder} |
| /// |
| /// {@macro flutter.widgets.PageView.findChildIndexCallback} |
| /// |
| /// The `addAutomaticKeepAlives` argument corresponds to the |
| /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The |
| /// `addRepaintBoundaries` argument corresponds to the |
| /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The |
| /// `addSemanticIndexes` argument corresponds to the |
| /// [SliverChildBuilderDelegate.addSemanticIndexes] property. |
| /// |
| /// {@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 |
| /// SliverList.builder( |
| /// itemBuilder: (BuildContext context, int index) { |
| /// return Container( |
| /// alignment: Alignment.center, |
| /// color: Colors.lightBlue[100 * (index % 9)], |
| /// child: Text('list item $index'), |
| /// ); |
| /// }, |
| /// ) |
| /// ``` |
| /// {@end-tool} |
| SliverList.builder({ |
| super.key, |
| required NullableIndexedWidgetBuilder itemBuilder, |
| ChildIndexGetter? findChildIndexCallback, |
| int? itemCount, |
| bool addAutomaticKeepAlives = true, |
| bool addRepaintBoundaries = true, |
| bool addSemanticIndexes = true, |
| }) : super(delegate: SliverChildBuilderDelegate( |
| itemBuilder, |
| findChildIndexCallback: findChildIndexCallback, |
| childCount: itemCount, |
| addAutomaticKeepAlives: addAutomaticKeepAlives, |
| addRepaintBoundaries: addRepaintBoundaries, |
| addSemanticIndexes: addSemanticIndexes, |
| )); |
| |
| /// A sliver that places multiple box children, separated by box widgets, in a |
| /// linear array along the main axis. |
| /// |
| /// This constructor is appropriate for sliver lists with a large (or |
| /// infinite) number of children because the builder is called only for those |
| /// children that are actually visible. |
| /// |
| /// Providing a non-null `itemCount` improves the ability of the [SliverGrid] |
| /// to estimate the maximum scroll extent. |
| /// |
| /// `itemBuilder` will be called only with indices greater than or equal to |
| /// zero and less than `itemCount`. |
| /// |
| /// {@macro flutter.widgets.ListView.builder.itemBuilder} |
| /// |
| /// {@macro flutter.widgets.PageView.findChildIndexCallback} |
| /// |
| /// |
| /// The `separatorBuilder` is similar to `itemBuilder`, except it is the widget |
| /// that gets placed between itemBuilder(context, index) and itemBuilder(context, index + 1). |
| /// |
| /// The `addAutomaticKeepAlives` argument corresponds to the |
| /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The |
| /// `addRepaintBoundaries` argument corresponds to the |
| /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The |
| /// `addSemanticIndexes` argument corresponds to the |
| /// [SliverChildBuilderDelegate.addSemanticIndexes] property. |
| /// {@tool snippet} |
| /// |
| /// This example shows how to create a [SliverList] whose [Container] items |
| /// are separated by [Divider]s. |
| /// |
| /// ```dart |
| /// SliverList.separated( |
| /// itemBuilder: (BuildContext context, int index) { |
| /// return Container( |
| /// alignment: Alignment.center, |
| /// color: Colors.lightBlue[100 * (index % 9)], |
| /// child: Text('list item $index'), |
| /// ); |
| /// }, |
| /// separatorBuilder: (BuildContext context, int index) => const Divider(), |
| /// ) |
| /// ``` |
| /// {@end-tool} |
| SliverList.separated({ |
| super.key, |
| required NullableIndexedWidgetBuilder itemBuilder, |
| ChildIndexGetter? findChildIndexCallback, |
| required NullableIndexedWidgetBuilder separatorBuilder, |
| int? itemCount, |
| bool addAutomaticKeepAlives = true, |
| bool addRepaintBoundaries = true, |
| bool addSemanticIndexes = true, |
| }) : super(delegate: SliverChildBuilderDelegate( |
| (BuildContext context, int index) { |
| final int itemIndex = index ~/ 2; |
| final Widget? widget; |
| if (index.isEven) { |
| widget = itemBuilder(context, itemIndex); |
| } else { |
| widget = separatorBuilder(context, itemIndex); |
| assert(() { |
| if (widget == null) { |
| throw FlutterError('separatorBuilder cannot return null.'); |
| } |
| return true; |
| }()); |
| } |
| return widget; |
| }, |
| findChildIndexCallback: findChildIndexCallback, |
| childCount: itemCount == null ? null : math.max(0, itemCount * 2 - 1), |
| addAutomaticKeepAlives: addAutomaticKeepAlives, |
| addRepaintBoundaries: addRepaintBoundaries, |
| addSemanticIndexes: addSemanticIndexes, |
| semanticIndexCallback: (Widget _, int index) { |
| return index.isEven ? index ~/ 2 : null; |
| }, |
| )); |
| |
| /// A sliver that places multiple box children in a linear array along the main |
| /// axis. |
| /// |
| /// This constructor uses a list of [Widget]s to build the sliver. |
| /// |
| /// The `addAutomaticKeepAlives` argument corresponds to the |
| /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The |
| /// `addRepaintBoundaries` argument corresponds to the |
| /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The |
| /// `addSemanticIndexes` argument corresponds to the |
| /// [SliverChildBuilderDelegate.addSemanticIndexes] property. |
| /// |
| /// {@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 |
| /// SliverList.list( |
| /// children: const <Widget>[ |
| /// Text('Hello'), |
| /// Text('World!'), |
| /// ], |
| /// ); |
| /// ``` |
| /// {@end-tool} |
| SliverList.list({ |
| super.key, |
| required List<Widget> children, |
| bool addAutomaticKeepAlives = true, |
| bool addRepaintBoundaries = true, |
| bool addSemanticIndexes = true, |
| }) : super(delegate: SliverChildListDelegate( |
| children, |
| addAutomaticKeepAlives: addAutomaticKeepAlives, |
| addRepaintBoundaries: addRepaintBoundaries, |
| addSemanticIndexes: addSemanticIndexes, |
| )); |
| |
| @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. |
| /// |
| /// _To learn more about slivers, see [CustomScrollView.slivers]._ |
| /// |
| /// [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, |
| }); |
| |
| /// A sliver that places multiple box children in a linear array along the main |
| /// axis. |
| /// |
| /// [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. |
| /// |
| /// This constructor is appropriate for sliver lists with a large (or |
| /// infinite) number of children whose extent is already determined. |
| /// |
| /// Providing a non-null `itemCount` improves the ability of the [SliverGrid] |
| /// to estimate the maximum scroll extent. |
| /// |
| /// `itemBuilder` will be called only with indices greater than or equal to |
| /// zero and less than `itemCount`. |
| /// |
| /// {@macro flutter.widgets.ListView.builder.itemBuilder} |
| /// |
| /// The `itemExtent` argument is the extent of each item. |
| /// |
| /// {@macro flutter.widgets.PageView.findChildIndexCallback} |
| /// |
| /// The `addAutomaticKeepAlives` argument corresponds to the |
| /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The |
| /// `addRepaintBoundaries` argument corresponds to the |
| /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The |
| /// `addSemanticIndexes` argument corresponds to the |
| /// [SliverChildBuilderDelegate.addSemanticIndexes] property. |
| /// {@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.builder( |
| /// itemExtent: 50.0, |
| /// itemBuilder: (BuildContext context, int index) { |
| /// return Container( |
| /// alignment: Alignment.center, |
| /// color: Colors.lightBlue[100 * (index % 9)], |
| /// child: Text('list item $index'), |
| /// ); |
| /// }, |
| /// ) |
| /// ``` |
| /// {@end-tool} |
| SliverFixedExtentList.builder({ |
| super.key, |
| required NullableIndexedWidgetBuilder itemBuilder, |
| required this.itemExtent, |
| ChildIndexGetter? findChildIndexCallback, |
| int? itemCount, |
| bool addAutomaticKeepAlives = true, |
| bool addRepaintBoundaries = true, |
| bool addSemanticIndexes = true, |
| }) : super(delegate: SliverChildBuilderDelegate( |
| itemBuilder, |
| findChildIndexCallback: findChildIndexCallback, |
| childCount: itemCount, |
| addAutomaticKeepAlives: addAutomaticKeepAlives, |
| addRepaintBoundaries: addRepaintBoundaries, |
| addSemanticIndexes: addSemanticIndexes, |
| )); |
| |
| /// A sliver that places multiple box children in a linear array along the main |
| /// axis. |
| /// |
| /// [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. |
| /// |
| /// This constructor uses a list of [Widget]s to build the sliver. |
| /// |
| /// The `addAutomaticKeepAlives` argument corresponds to the |
| /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The |
| /// `addRepaintBoundaries` argument corresponds to the |
| /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The |
| /// `addSemanticIndexes` argument corresponds to the |
| /// [SliverChildBuilderDelegate.addSemanticIndexes] property. |
| /// |
| /// {@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.list( |
| /// itemExtent: 50.0, |
| /// children: const <Widget>[ |
| /// Text('Hello'), |
| /// Text('World!'), |
| /// ], |
| /// ); |
| /// ``` |
| /// {@end-tool} |
| SliverFixedExtentList.list({ |
| super.key, |
| required List<Widget> children, |
| required this.itemExtent, |
| bool addAutomaticKeepAlives = true, |
| bool addRepaintBoundaries = true, |
| bool addSemanticIndexes = true, |
| }) : super(delegate: SliverChildListDelegate( |
| children, |
| addAutomaticKeepAlives: addAutomaticKeepAlives, |
| addRepaintBoundaries: addRepaintBoundaries, |
| addSemanticIndexes: addSemanticIndexes, |
| )); |
| |
| /// 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. |
| /// |
| /// _To learn more about slivers, see [CustomScrollView.slivers]._ |
| /// |
| /// [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, |
| }); |
| |
| /// A sliver that creates a 2D array of widgets that are created on demand. |
| /// |
| /// This constructor is appropriate for sliver grids with a large (or |
| /// infinite) number of children because the builder is called only for those |
| /// children that are actually visible. |
| /// |
| /// Providing a non-null `itemCount` improves the ability of the [SliverGrid] |
| /// to estimate the maximum scroll extent. |
| /// |
| /// `itemBuilder` will be called only with indices greater than or equal to |
| /// zero and less than `itemCount`. |
| /// |
| /// {@macro flutter.widgets.ListView.builder.itemBuilder} |
| /// |
| /// {@macro flutter.widgets.PageView.findChildIndexCallback} |
| /// |
| /// The [gridDelegate] argument is required. |
| /// |
| /// The `addAutomaticKeepAlives` argument corresponds to the |
| /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The |
| /// `addRepaintBoundaries` argument corresponds to the |
| /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The |
| /// `addSemanticIndexes` argument corresponds to the |
| /// [SliverChildBuilderDelegate.addSemanticIndexes] property. |
| SliverGrid.builder({ |
| super.key, |
| required this.gridDelegate, |
| required NullableIndexedWidgetBuilder itemBuilder, |
| ChildIndexGetter? findChildIndexCallback, |
| int? itemCount, |
| bool addAutomaticKeepAlives = true, |
| bool addRepaintBoundaries = true, |
| bool addSemanticIndexes = true, |
| }) : super(delegate: SliverChildBuilderDelegate( |
| itemBuilder, |
| findChildIndexCallback: findChildIndexCallback, |
| childCount: itemCount, |
| addAutomaticKeepAlives: addAutomaticKeepAlives, |
| addRepaintBoundaries: addRepaintBoundaries, |
| addSemanticIndexes: addSemanticIndexes, |
| )); |
| |
| /// 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.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(_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(_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; |
| case Axis.vertical: |
| itemExtent = child.renderObject!.paintBounds.height; |
| } |
| |
| 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 not painted at all. |
| /// For the value 1.0, the sliver child is painted immediately without an |
| /// intermediate buffer. |
| /// |
| /// {@tool dartpad} |
| /// |
| /// This example shows a [SliverList] when the `_visible` member field is true, |
| /// and hides it when it is false. |
| /// |
| /// This is more efficient than adding and removing the sliver child widget from |
| /// the tree on demand, but it does not affect how much the list scrolls (the |
| /// [SliverList] is still present, merely invisible). |
| /// |
| /// ** See code in examples/api/lib/widgets/sliver/sliver_opacity.1.dart ** |
| /// {@end-tool} |
| /// |
| /// 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]. |
| /// * [SliverVisibility], which can hide a child more efficiently (albeit less |
| /// subtly, because it is either visible or hidden, rather than allowing |
| /// fractional opacity values). Specifically, the [SliverVisibility.maintain] |
| /// constructor is equivalent to using a sliver opacity widget with values of |
| /// `0.0` or `1.0`. |
| 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 >= 0.0 && opacity <= 1.0), |
| 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]. |
| /// |
| /// {@macro flutter.widgets.IgnorePointer.Semantics} |
| /// |
| /// See also: |
| /// |
| /// * [IgnorePointer], the equivalent widget for boxes. |
| class SliverIgnorePointer extends SingleChildRenderObjectWidget { |
| /// Creates a sliver widget that is invisible to hit testing. |
| /// |
| /// The [ignoring] argument must not be null. |
| const SliverIgnorePointer({ |
| super.key, |
| this.ignoring = true, |
| @Deprecated( |
| 'Create a custom sliver ignore pointer widget instead. ' |
| 'This feature was deprecated after v3.8.0-12.0.pre.' |
| ) |
| this.ignoringSemantics, |
| Widget? sliver, |
| }) : 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. |
| /// |
| /// {@macro flutter.widgets.IgnorePointer.Semantics} |
| final bool ignoring; |
| |
| /// Whether the semantics of this sliver is ignored when compiling the |
| /// semantics tree. |
| /// |
| /// {@macro flutter.widgets.IgnorePointer.Semantics} |
| @Deprecated( |
| 'Create a custom sliver ignore pointer widget instead. ' |
| 'This feature was deprecated after v3.8.0-12.0.pre.' |
| ) |
| 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. |
| /// |
| /// See also: |
| /// |
| /// * [Offstage], the equivalent widget for boxes. |
| class SliverOffstage extends SingleChildRenderObjectWidget { |
| /// Creates a sliver that visually hides its sliver child. |
| const SliverOffstage({ |
| super.key, |
| this.offstage = true, |
| Widget? sliver, |
| }) : 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, |
| }); |
| |
| /// 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) { |
| // No need to redo layout if it became true. |
| parentData.keepAlive = keepAlive; |
| final RenderObject? targetParent = renderObject.parent; |
| if (targetParent is RenderObject && !keepAlive) { |
| targetParent.markNeedsLayout(); |
| } |
| } |
| } |
| |
| // 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)); |
| } |
| } |
| |
| /// A sliver that constrains the cross axis extent of its sliver child. |
| /// |
| /// The [SliverConstrainedCrossAxis] takes a [maxExtent] parameter and uses it as |
| /// the cross axis extent of the [SliverConstraints] passed to the sliver child. |
| /// The widget ensures that the [maxExtent] is a nonnegative value. |
| /// |
| /// This is useful when you want to apply a custom cross-axis extent constraint |
| /// to a sliver child, as slivers typically consume the full cross axis extent. |
| /// |
| /// This widget also sets its parent data's [SliverPhysicalParentData.crossAxisFlex] |
| /// to 0, so that it informs [SliverCrossAxisGroup] that it should not flex |
| /// in the cross axis direction. |
| /// |
| /// {@tool dartpad} |
| /// In this sample the [SliverConstrainedCrossAxis] sizes its child so that the |
| /// cross axis extent takes up less space than the actual viewport. |
| /// |
| /// ** See code in examples/api/lib/widgets/sliver/sliver_constrained_cross_axis.0.dart ** |
| /// {@end-tool} |
| /// |
| /// See also: |
| /// |
| /// * [SliverCrossAxisGroup], the widget which makes use of 0 flex factor set by |
| /// this widget. |
| class SliverConstrainedCrossAxis extends StatelessWidget { |
| /// Creates a sliver that constrains the cross axis extent of its sliver child. |
| /// |
| /// The [maxExtent] parameter is required and must be nonnegative. |
| const SliverConstrainedCrossAxis({ |
| super.key, |
| required this.maxExtent, |
| required this.sliver, |
| }); |
| |
| /// The cross axis extent to apply to the sliver child. |
| /// |
| /// This value must be nonnegative. |
| final double maxExtent; |
| |
| /// The widget below this widget in the tree. |
| /// |
| /// Must be a sliver. |
| final Widget sliver; |
| |
| @override |
| Widget build(BuildContext context) { |
| return _SliverZeroFlexParentDataWidget( |
| sliver: _SliverConstrainedCrossAxis( |
| maxExtent: maxExtent, |
| sliver: sliver, |
| ) |
| ); |
| } |
| } |
| class _SliverZeroFlexParentDataWidget extends ParentDataWidget<SliverPhysicalParentData> { |
| const _SliverZeroFlexParentDataWidget({ |
| required Widget sliver, |
| }) : super(child: sliver); |
| |
| @override |
| void applyParentData(RenderObject renderObject) { |
| assert(renderObject.parentData is SliverPhysicalParentData); |
| final SliverPhysicalParentData parentData = renderObject.parentData! as SliverPhysicalParentData; |
| bool needsLayout = false; |
| if (parentData.crossAxisFlex != 0) { |
| parentData.crossAxisFlex = 0; |
| needsLayout = true; |
| } |
| |
| if (needsLayout) { |
| final RenderObject? targetParent = renderObject.parent; |
| if (targetParent is RenderObject) { |
| targetParent.markNeedsLayout(); |
| } |
| |
| } |
| } |
| |
| @override |
| Type get debugTypicalAncestorWidgetClass => SliverCrossAxisGroup; |
| } |
| |
| class _SliverConstrainedCrossAxis extends SingleChildRenderObjectWidget { |
| const _SliverConstrainedCrossAxis({ |
| required this.maxExtent, |
| required Widget sliver, |
| }) : assert(maxExtent >= 0.0), |
| super(child: sliver); |
| |
| /// The cross axis extent to apply to the sliver child. |
| /// |
| /// This value must be nonnegative. |
| final double maxExtent; |
| |
| @override |
| RenderSliverConstrainedCrossAxis createRenderObject(BuildContext context) { |
| return RenderSliverConstrainedCrossAxis(maxExtent: maxExtent); |
| } |
| |
| @override |
| void updateRenderObject(BuildContext context, RenderSliverConstrainedCrossAxis renderObject) { |
| renderObject.maxExtent = maxExtent; |
| } |
| } |
| |
| /// Set a flex factor for allocating space in the cross axis direction. |
| /// |
| /// This is a [ParentDataWidget] to be used in [SliverCrossAxisGroup]. |
| /// After all slivers with null or zero flex (e.g. [SliverConstrainedCrossAxis]) |
| /// are laid out (which should determine their own [SliverGeometry.crossAxisExtent]), |
| /// the remaining space is laid out among the slivers with nonzero flex |
| /// proportionally to their flex value. |
| class SliverCrossAxisExpanded extends ParentDataWidget<SliverPhysicalContainerParentData> { |
| /// Creates an object that assigns a [flex] value to the child sliver. |
| /// |
| /// The provided [flex] value must be greater than 0. |
| const SliverCrossAxisExpanded({ |
| super.key, |
| required this.flex, |
| required Widget sliver, |
| }): assert(flex > 0 && flex < double.infinity), |
| super(child: sliver); |
| |
| /// Flex value for allocating cross axis extent left after laying out the children with |
| /// constrained cross axis. The children with flex values will have the remaining extent |
| /// allocated proportionally to their flex value. This must an integer between |
| /// 0 and infinity, exclusive. |
| final int flex; |
| |
| @override |
| void applyParentData(RenderObject renderObject) { |
| assert(renderObject.parentData is SliverPhysicalContainerParentData); |
| assert(renderObject.parent is RenderSliverCrossAxisGroup); |
| final SliverPhysicalParentData parentData = renderObject.parentData! as SliverPhysicalParentData; |
| bool needsLayout = false; |
| |
| if (parentData.crossAxisFlex != flex) { |
| parentData.crossAxisFlex = flex; |
| needsLayout = true; |
| } |
| |
| if (needsLayout) { |
| final RenderObject? targetParent = renderObject.parent; |
| if (targetParent is RenderObject) { |
| targetParent.markNeedsLayout(); |
| } |
| } |
| } |
| |
| @override |
| Type get debugTypicalAncestorWidgetClass => SliverCrossAxisGroup; |
| } |
| |
| |
| /// A sliver that places multiple sliver children in a linear array along |
| /// the cross axis. |
| /// |
| /// ## Layout algorithm |
| /// |
| /// _This section describes how the framework causes [RenderSliverCrossAxisGroup] |
| /// to position its children._ |
| /// |
| /// Layout for a [RenderSliverCrossAxisGroup] has four steps: |
| /// |
| /// 1. Layout each child with a null or zero flex factor with cross axis constraint |
| /// being whatever cross axis space is remaining after laying out any previous |
| /// sliver. Slivers with null or zero flex factor should determine their own |
| /// [SliverGeometry.crossAxisExtent]. For example, the [SliverConstrainedCrossAxis] |
| /// widget uses either [SliverConstrainedCrossAxis.maxExtent] or |
| /// [SliverConstraints.crossAxisExtent], deciding between whichever is smaller. |
| /// 2. Divide up the remaining cross axis space among the children with non-zero flex |
| /// factors according to their flex factor. For example, a child with a flex |
| /// factor of 2.0 will receive twice the amount of cross axis space as a child |
| /// with a flex factor 1.0. |
| /// 3. Layout each of the remaining children with the cross axis constraint |
| /// allocated in the previous step. |
| /// 4. Set the geometry to that of whichever child has the longest |
| /// [SliverGeometry.scrollExtent] with the [SliverGeometry.crossAxisExtent] adjusted |
| /// to [SliverConstraints.crossAxisExtent]. |
| /// |
| /// {@tool dartpad} |
| /// In this sample the [SliverCrossAxisGroup] sizes its three [children] so that |
| /// the first normal [SliverList] has a flex factor of 1, the second [SliverConstrainedCrossAxis] |
| /// has a flex factor of 0 and a maximum cross axis extent of 200.0, and the third |
| /// [SliverCrossAxisExpanded] has a flex factor of 2. |
| /// |
| /// ** See code in examples/api/lib/widgets/sliver/sliver_cross_axis_group.0.dart ** |
| /// {@end-tool} |
| /// |
| /// See also: |
| /// |
| /// * [SliverCrossAxisExpanded], which is the [ParentDataWidget] for setting a flex |
| /// value to a widget. |
| /// * [SliverConstrainedCrossAxis], which is a [RenderObjectWidget] for setting |
| /// an extent to constrain the widget to. |
| /// * [SliverMainAxisGroup], which is the [RenderObjectWidget] for laying out |
| /// multiple slivers along the main axis. |
| class SliverCrossAxisGroup extends MultiChildRenderObjectWidget { |
| /// Creates a sliver that places sliver children in a linear array along |
| /// the cross axis. |
| const SliverCrossAxisGroup({ |
| super.key, |
| required List<Widget> slivers, |
| }): super(children: slivers); |
| |
| @override |
| RenderSliverCrossAxisGroup createRenderObject(BuildContext context) { |
| return RenderSliverCrossAxisGroup(); |
| } |
| } |
| |
| /// A sliver that places multiple sliver children in a linear array along |
| /// the main axis, one after another. |
| /// |
| /// ## Layout algorithm |
| /// |
| /// _This section describes how the framework causes [RenderSliverMainAxisGroup] |
| /// to position its children._ |
| /// |
| /// Layout for a [RenderSliverMainAxisGroup] has four steps: |
| /// |
| /// 1. Keep track of an offset variable which is the total [SliverGeometry.scrollExtent] |
| /// of the slivers laid out so far. |
| /// 2. To determine the constraints for the next sliver child to layout, calculate the |
| /// amount of paint extent occupied from 0.0 to the offset variable and subtract this from |
| /// [SliverConstraints.remainingPaintExtent] minus to use as the child's |
| /// [SliverConstraints.remainingPaintExtent]. For the [SliverConstraints.scrollOffset], |
| /// take the provided constraint's value and subtract out the offset variable, using |
| /// 0.0 if negative. |
| /// 3. Once we finish laying out all the slivers, this offset variable represents |
| /// the total [SliverGeometry.scrollExtent] of the sliver group. Since it is possible |
| /// for specialized slivers to try to paint itself outside of the bounds of the |
| /// sliver group's scroll extent (see [SliverPersistentHeader]), we must do a |
| /// second pass to set a [SliverPhysicalParentData.paintOffset] to make sure it |
| /// is within the bounds of the sliver group. |
| /// 4. Finally, set the [RenderSliverMainAxisGroup.geometry] with the total |
| /// [SliverGeometry.scrollExtent], [SliverGeometry.paintExtent] calculated from |
| /// the constraints and [SliverGeometry.scrollExtent], and [SliverGeometry.maxPaintExtent]. |
| /// |
| /// {@tool dartpad} |
| /// In this sample the [CustomScrollView] renders a [SliverMainAxisGroup] and a |
| /// [SliverToBoxAdapter] with some content. The [SliverMainAxisGroup] renders a |
| /// [SliverAppBar], [SliverList], and [SliverToBoxAdapter]. Notice that when the |
| /// [SliverMainAxisGroup] goes out of view, so does the pinned [SliverAppBar]. |
| /// |
| /// ** See code in examples/api/lib/widgets/sliver/sliver_main_axis_group.0.dart ** |
| /// {@end-tool} |
| /// |
| /// See also: |
| /// |
| /// * [SliverPersistentHeader], which is a [RenderObjectWidget] which may require |
| /// adjustment to its [SliverPhysicalParentData.paintOffset] to make it fit |
| /// within the computed [SliverGeometry.scrollExtent] of the [SliverMainAxisGroup]. |
| /// * [SliverCrossAxisGroup], which is the [RenderObjectWidget] for laying out |
| /// multiple slivers along the cross axis. |
| class SliverMainAxisGroup extends MultiChildRenderObjectWidget { |
| /// Creates a sliver that places sliver children in a linear array along |
| /// the main axis. |
| const SliverMainAxisGroup({ |
| super.key, |
| required List<Widget> slivers, |
| }) : super(children: slivers); |
| |
| @override |
| RenderSliverMainAxisGroup createRenderObject(BuildContext context) { |
| return RenderSliverMainAxisGroup(); |
| } |
| } |