
Going to make some changes to the implementation so I'll revert in the meantime. Reverts flutter/flutter#124337
1368 lines
51 KiB
Dart
1368 lines
51 KiB
Dart
// 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 AbstractNode? 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));
|
|
}
|
|
}
|