Clean up scrolling delegate code for 2D (#122651)
Clean up scrolling delegate code for 2D
This commit is contained in:
parent
0253bf0f05
commit
68be52c5f5
@ -7,6 +7,7 @@ import 'package:flutter/foundation.dart';
|
||||
import 'basic.dart';
|
||||
import 'framework.dart';
|
||||
import 'scroll_controller.dart';
|
||||
import 'scroll_delegate.dart';
|
||||
import 'scroll_physics.dart';
|
||||
import 'scroll_view.dart';
|
||||
import 'sliver.dart';
|
||||
|
@ -16,6 +16,7 @@ import 'page_storage.dart';
|
||||
import 'scroll_configuration.dart';
|
||||
import 'scroll_context.dart';
|
||||
import 'scroll_controller.dart';
|
||||
import 'scroll_delegate.dart';
|
||||
import 'scroll_metrics.dart';
|
||||
import 'scroll_notification.dart';
|
||||
import 'scroll_physics.dart';
|
||||
@ -23,7 +24,6 @@ import 'scroll_position.dart';
|
||||
import 'scroll_position_with_single_context.dart';
|
||||
import 'scroll_view.dart';
|
||||
import 'scrollable.dart';
|
||||
import 'sliver.dart';
|
||||
import 'sliver_fill.dart';
|
||||
import 'viewport.dart';
|
||||
|
||||
|
@ -13,6 +13,7 @@ import 'inherited_theme.dart';
|
||||
import 'media_query.dart';
|
||||
import 'overlay.dart';
|
||||
import 'scroll_controller.dart';
|
||||
import 'scroll_delegate.dart';
|
||||
import 'scroll_physics.dart';
|
||||
import 'scroll_view.dart';
|
||||
import 'scrollable.dart';
|
||||
|
863
packages/flutter/lib/src/widgets/scroll_delegate.dart
Normal file
863
packages/flutter/lib/src/widgets/scroll_delegate.dart
Normal file
@ -0,0 +1,863 @@
|
||||
// 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 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
import 'automatic_keep_alive.dart';
|
||||
import 'basic.dart';
|
||||
import 'framework.dart';
|
||||
import 'selection_container.dart';
|
||||
|
||||
export 'package:flutter/rendering.dart' show
|
||||
SliverGridDelegate,
|
||||
SliverGridDelegateWithFixedCrossAxisCount,
|
||||
SliverGridDelegateWithMaxCrossAxisExtent;
|
||||
|
||||
// Examples can assume:
|
||||
// late SliverGridDelegateWithMaxCrossAxisExtent _gridDelegate;
|
||||
// abstract class SomeWidget extends StatefulWidget { const SomeWidget({super.key}); }
|
||||
// typedef ChildWidget = Placeholder;
|
||||
|
||||
/// A callback which produces a semantic index given a widget and the local index.
|
||||
///
|
||||
/// Return a null value to prevent a widget from receiving an index.
|
||||
///
|
||||
/// A semantic index is used to tag child semantic nodes for accessibility
|
||||
/// announcements in scroll view.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [CustomScrollView], for an explanation of scroll semantics.
|
||||
/// * [SliverChildBuilderDelegate], for an explanation of how this is used to
|
||||
/// generate indexes.
|
||||
typedef SemanticIndexCallback = int? Function(Widget widget, int localIndex);
|
||||
|
||||
int _kDefaultSemanticIndexCallback(Widget _, int localIndex) => localIndex;
|
||||
|
||||
/// A delegate that supplies children for slivers.
|
||||
///
|
||||
/// Many slivers lazily construct their box children to avoid creating more
|
||||
/// children than are visible through the [Viewport]. Rather than receiving
|
||||
/// their children as an explicit [List], they receive their children using a
|
||||
/// [SliverChildDelegate].
|
||||
///
|
||||
/// It's uncommon to subclass [SliverChildDelegate]. Instead, consider using one
|
||||
/// of the existing subclasses that provide adaptors to builder callbacks or
|
||||
/// explicit child lists.
|
||||
///
|
||||
/// {@template flutter.widgets.SliverChildDelegate.lifecycle}
|
||||
/// ## Child elements' lifecycle
|
||||
///
|
||||
/// ### Creation
|
||||
///
|
||||
/// While laying out the list, visible children's elements, states and render
|
||||
/// objects will be created lazily based on existing widgets (such as in the
|
||||
/// case of [SliverChildListDelegate]) or lazily provided ones (such as in the
|
||||
/// case of [SliverChildBuilderDelegate]).
|
||||
///
|
||||
/// ### Destruction
|
||||
///
|
||||
/// When a child is scrolled out of view, the associated element subtree, states
|
||||
/// and render objects are destroyed. A new child at the same position in the
|
||||
/// sliver will be lazily recreated along with new elements, states and render
|
||||
/// objects when it is scrolled back.
|
||||
///
|
||||
/// ### Destruction mitigation
|
||||
///
|
||||
/// In order to preserve state as child elements are scrolled in and out of
|
||||
/// view, the following options are possible:
|
||||
///
|
||||
/// * Moving the ownership of non-trivial UI-state-driving business logic
|
||||
/// out of the sliver child subtree. For instance, if a list contains posts
|
||||
/// with their number of upvotes coming from a cached network response, store
|
||||
/// the list of posts and upvote number in a data model outside the list. Let
|
||||
/// the sliver child UI subtree be easily recreate-able from the
|
||||
/// source-of-truth model object. Use [StatefulWidget]s in the child widget
|
||||
/// subtree to store instantaneous UI state only.
|
||||
///
|
||||
/// * Letting [KeepAlive] be the root widget of the sliver child widget subtree
|
||||
/// that needs to be preserved. The [KeepAlive] widget marks the child
|
||||
/// subtree's top render object child for keepalive. When the associated top
|
||||
/// render object is scrolled out of view, the sliver keeps the child's
|
||||
/// render object (and by extension, its associated elements and states) in a
|
||||
/// cache list instead of destroying them. When scrolled back into view, the
|
||||
/// render object is repainted as-is (if it wasn't marked dirty in the
|
||||
/// interim).
|
||||
///
|
||||
/// This only works if the [SliverChildDelegate] subclasses don't wrap the
|
||||
/// child widget subtree with other widgets such as [AutomaticKeepAlive] and
|
||||
/// [RepaintBoundary] via `addAutomaticKeepAlives` and
|
||||
/// `addRepaintBoundaries`.
|
||||
///
|
||||
/// * Using [AutomaticKeepAlive] widgets (inserted by default in
|
||||
/// [SliverChildListDelegate] or [SliverChildListDelegate]).
|
||||
/// [AutomaticKeepAlive] allows descendant widgets to control whether the
|
||||
/// subtree is actually kept alive or not. This behavior is in contrast with
|
||||
/// [KeepAlive], which will unconditionally keep the subtree alive.
|
||||
///
|
||||
/// As an example, the [EditableText] widget signals its sliver child element
|
||||
/// subtree to stay alive while its text field has input focus. If it doesn't
|
||||
/// have focus and no other descendants signaled for keepalive via a
|
||||
/// [KeepAliveNotification], the sliver child element subtree will be
|
||||
/// destroyed when scrolled away.
|
||||
///
|
||||
/// [AutomaticKeepAlive] descendants typically signal it to be kept alive by
|
||||
/// using the [AutomaticKeepAliveClientMixin], then implementing the
|
||||
/// [AutomaticKeepAliveClientMixin.wantKeepAlive] getter and calling
|
||||
/// [AutomaticKeepAliveClientMixin.updateKeepAlive].
|
||||
///
|
||||
/// ## Using more than one delegate in a [Viewport]
|
||||
///
|
||||
/// If multiple delegates are used in a single scroll view, the first child of
|
||||
/// each delegate will always be laid out, even if it extends beyond the
|
||||
/// currently viewable area. This is because at least one child is required in
|
||||
/// order to [estimateMaxScrollOffset] for the whole scroll view, as it uses the
|
||||
/// currently built children to estimate the remaining children's extent.
|
||||
/// {@endtemplate}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [SliverChildBuilderDelegate], which is a delegate that uses a builder
|
||||
/// callback to construct the children.
|
||||
/// * [SliverChildListDelegate], which is a delegate that has an explicit list
|
||||
/// of children.
|
||||
abstract class SliverChildDelegate {
|
||||
/// Abstract const constructor. This constructor enables subclasses to provide
|
||||
/// const constructors so that they can be used in const expressions.
|
||||
const SliverChildDelegate();
|
||||
|
||||
/// Returns the child with the given index.
|
||||
///
|
||||
/// Should return null if asked to build a widget with a greater
|
||||
/// index than exists. If this returns null, [estimatedChildCount]
|
||||
/// must subsequently return a precise non-null value (which is then
|
||||
/// used to implement [RenderSliverBoxChildManager.childCount]).
|
||||
///
|
||||
/// Subclasses typically override this function and wrap their children in
|
||||
/// [AutomaticKeepAlive], [IndexedSemantics], and [RepaintBoundary] widgets.
|
||||
///
|
||||
/// The values returned by this method are cached. To indicate that the
|
||||
/// widgets have changed, a new delegate must be provided, and the new
|
||||
/// delegate's [shouldRebuild] method must return true.
|
||||
Widget? build(BuildContext context, int index);
|
||||
|
||||
/// Returns an estimate of the number of children this delegate will build.
|
||||
///
|
||||
/// Used to estimate the maximum scroll offset if [estimateMaxScrollOffset]
|
||||
/// returns null.
|
||||
///
|
||||
/// Return null if there are an unbounded number of children or if it would
|
||||
/// be too difficult to estimate the number of children.
|
||||
///
|
||||
/// This must return a precise number once [build] has returned null, as it
|
||||
/// used to implement [RenderSliverBoxChildManager.childCount].
|
||||
int? get estimatedChildCount => null;
|
||||
|
||||
/// Returns an estimate of the max scroll extent for all the children.
|
||||
///
|
||||
/// Subclasses should override this function if they have additional
|
||||
/// information about their max scroll extent.
|
||||
///
|
||||
/// The default implementation returns null, which causes the caller to
|
||||
/// extrapolate the max scroll offset from the given parameters.
|
||||
double? estimateMaxScrollOffset(
|
||||
int firstIndex,
|
||||
int lastIndex,
|
||||
double leadingScrollOffset,
|
||||
double trailingScrollOffset,
|
||||
) => null;
|
||||
|
||||
/// Called at the end of layout to indicate that layout is now complete.
|
||||
///
|
||||
/// The `firstIndex` argument is the index of the first child that was
|
||||
/// included in the current layout. The `lastIndex` argument is the index of
|
||||
/// the last child that was included in the current layout.
|
||||
///
|
||||
/// Useful for subclasses that which to track which children are included in
|
||||
/// the underlying render tree.
|
||||
void didFinishLayout(int firstIndex, int lastIndex) { }
|
||||
|
||||
/// Called whenever a new instance of the child delegate class is
|
||||
/// provided to the sliver.
|
||||
///
|
||||
/// If the new instance represents different information than the old
|
||||
/// instance, then the method should return true, otherwise it should return
|
||||
/// false.
|
||||
///
|
||||
/// If the method returns false, then the [build] call might be optimized
|
||||
/// away.
|
||||
bool shouldRebuild(covariant SliverChildDelegate oldDelegate);
|
||||
|
||||
/// Find index of child element with associated key.
|
||||
///
|
||||
/// This will be called during `performRebuild` in [SliverMultiBoxAdaptorElement]
|
||||
/// to check if a child has moved to a different position. It should return the
|
||||
/// index of the child element with associated key, null if not found.
|
||||
///
|
||||
/// If not provided, a child widget may not map to its existing [RenderObject]
|
||||
/// when the order of children returned from the children builder changes.
|
||||
/// This may result in state-loss.
|
||||
int? findIndexByKey(Key key) => null;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
final List<String> description = <String>[];
|
||||
debugFillDescription(description);
|
||||
return '${describeIdentity(this)}(${description.join(", ")})';
|
||||
}
|
||||
|
||||
/// Add additional information to the given description for use by [toString].
|
||||
@protected
|
||||
@mustCallSuper
|
||||
void debugFillDescription(List<String> description) {
|
||||
try {
|
||||
final int? children = estimatedChildCount;
|
||||
if (children != null) {
|
||||
description.add('estimated child count: $children');
|
||||
}
|
||||
} catch (e) {
|
||||
// The exception is forwarded to widget inspector.
|
||||
description.add('estimated child count: EXCEPTION (${e.runtimeType})');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _SaltedValueKey extends ValueKey<Key> {
|
||||
const _SaltedValueKey(super.key);
|
||||
}
|
||||
|
||||
/// Called to find the new index of a child based on its `key` in case of
|
||||
/// reordering.
|
||||
///
|
||||
/// If the child with the `key` is no longer present, null is returned.
|
||||
///
|
||||
/// Used by [SliverChildBuilderDelegate.findChildIndexCallback].
|
||||
typedef ChildIndexGetter = int? Function(Key key);
|
||||
|
||||
/// A delegate that supplies children for slivers using a builder callback.
|
||||
///
|
||||
/// Many slivers lazily construct their box children to avoid creating more
|
||||
/// children than are visible through the [Viewport]. This delegate provides
|
||||
/// children using a [NullableIndexedWidgetBuilder] callback, so that the children do
|
||||
/// not even have to be built until they are displayed.
|
||||
///
|
||||
/// The widgets returned from the builder callback are automatically wrapped in
|
||||
/// [AutomaticKeepAlive] widgets if [addAutomaticKeepAlives] is true (the
|
||||
/// default) and in [RepaintBoundary] widgets if [addRepaintBoundaries] is true
|
||||
/// (also the default).
|
||||
///
|
||||
/// ## Accessibility
|
||||
///
|
||||
/// The [CustomScrollView] requires that its semantic children are annotated
|
||||
/// using [IndexedSemantics]. This is done by default in the delegate with
|
||||
/// the `addSemanticIndexes` parameter set to true.
|
||||
///
|
||||
/// If multiple delegates are used in a single scroll view, then the indexes
|
||||
/// will not be correct by default. The `semanticIndexOffset` can be used to
|
||||
/// offset the semantic indexes of each delegate so that the indexes are
|
||||
/// monotonically increasing. For example, if a scroll view contains two
|
||||
/// delegates where the first has 10 children contributing semantics, then the
|
||||
/// second delegate should offset its children by 10.
|
||||
///
|
||||
/// {@tool snippet}
|
||||
///
|
||||
/// This sample code shows how to use `semanticIndexOffset` to handle multiple
|
||||
/// delegates in a single scroll view.
|
||||
///
|
||||
/// ```dart
|
||||
/// CustomScrollView(
|
||||
/// semanticChildCount: 4,
|
||||
/// slivers: <Widget>[
|
||||
/// SliverGrid(
|
||||
/// gridDelegate: _gridDelegate,
|
||||
/// delegate: SliverChildBuilderDelegate(
|
||||
/// (BuildContext context, int index) {
|
||||
/// return const Text('...');
|
||||
/// },
|
||||
/// childCount: 2,
|
||||
/// ),
|
||||
/// ),
|
||||
/// SliverGrid(
|
||||
/// gridDelegate: _gridDelegate,
|
||||
/// delegate: SliverChildBuilderDelegate(
|
||||
/// (BuildContext context, int index) {
|
||||
/// return const Text('...');
|
||||
/// },
|
||||
/// childCount: 2,
|
||||
/// semanticIndexOffset: 2,
|
||||
/// ),
|
||||
/// ),
|
||||
/// ],
|
||||
/// )
|
||||
/// ```
|
||||
/// {@end-tool}
|
||||
///
|
||||
/// In certain cases, only a subset of child widgets should be annotated
|
||||
/// with a semantic index. For example, in [ListView.separated()] the
|
||||
/// separators do not have an index associated with them. This is done by
|
||||
/// providing a `semanticIndexCallback` which returns null for separators
|
||||
/// indexes and rounds the non-separator indexes down by half.
|
||||
///
|
||||
/// {@tool snippet}
|
||||
///
|
||||
/// This sample code shows how to use `semanticIndexCallback` to handle
|
||||
/// annotating a subset of child nodes with a semantic index. There is
|
||||
/// a [Spacer] widget at odd indexes which should not have a semantic
|
||||
/// index.
|
||||
///
|
||||
/// ```dart
|
||||
/// CustomScrollView(
|
||||
/// semanticChildCount: 5,
|
||||
/// slivers: <Widget>[
|
||||
/// SliverGrid(
|
||||
/// gridDelegate: _gridDelegate,
|
||||
/// delegate: SliverChildBuilderDelegate(
|
||||
/// (BuildContext context, int index) {
|
||||
/// if (index.isEven) {
|
||||
/// return const Text('...');
|
||||
/// }
|
||||
/// return const Spacer();
|
||||
/// },
|
||||
/// semanticIndexCallback: (Widget widget, int localIndex) {
|
||||
/// if (localIndex.isEven) {
|
||||
/// return localIndex ~/ 2;
|
||||
/// }
|
||||
/// return null;
|
||||
/// },
|
||||
/// childCount: 10,
|
||||
/// ),
|
||||
/// ),
|
||||
/// ],
|
||||
/// )
|
||||
/// ```
|
||||
/// {@end-tool}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [SliverChildListDelegate], which is a delegate that has an explicit list
|
||||
/// of children.
|
||||
/// * [IndexedSemantics], for an example of manually annotating child nodes
|
||||
/// with semantic indexes.
|
||||
class SliverChildBuilderDelegate extends SliverChildDelegate {
|
||||
/// Creates a delegate that supplies children for slivers using the given
|
||||
/// builder callback.
|
||||
///
|
||||
/// The [builder], [addAutomaticKeepAlives], [addRepaintBoundaries],
|
||||
/// [addSemanticIndexes], and [semanticIndexCallback] arguments must not be
|
||||
/// null.
|
||||
///
|
||||
/// If the order in which [builder] returns children ever changes, consider
|
||||
/// providing a [findChildIndexCallback]. This allows the delegate to find the
|
||||
/// new index for a child that was previously located at a different index to
|
||||
/// attach the existing state to the [Widget] at its new location.
|
||||
const SliverChildBuilderDelegate(
|
||||
this.builder, {
|
||||
this.findChildIndexCallback,
|
||||
this.childCount,
|
||||
this.addAutomaticKeepAlives = true,
|
||||
this.addRepaintBoundaries = true,
|
||||
this.addSemanticIndexes = true,
|
||||
this.semanticIndexCallback = _kDefaultSemanticIndexCallback,
|
||||
this.semanticIndexOffset = 0,
|
||||
});
|
||||
|
||||
/// Called to build children for the sliver.
|
||||
///
|
||||
/// Will be called only for indices greater than or equal to zero and less
|
||||
/// than [childCount] (if [childCount] is non-null).
|
||||
///
|
||||
/// Should return null if asked to build a widget with a greater index than
|
||||
/// exists.
|
||||
///
|
||||
/// May result in an infinite loop or run out of memory if [childCount] is null
|
||||
/// and the [builder] always provides a zero-size widget (such as `Container()`
|
||||
/// or `SizedBox.shrink()`). If possible, provide children with non-zero size,
|
||||
/// return null from [builder], or set a [childCount].
|
||||
///
|
||||
/// The delegate wraps the children returned by this builder in
|
||||
/// [RepaintBoundary] widgets.
|
||||
final NullableIndexedWidgetBuilder builder;
|
||||
|
||||
/// The total number of children this delegate can provide.
|
||||
///
|
||||
/// If null, the number of children is determined by the least index for which
|
||||
/// [builder] returns null.
|
||||
///
|
||||
/// May result in an infinite loop or run out of memory if [childCount] is null
|
||||
/// and the [builder] always provides a zero-size widget (such as `Container()`
|
||||
/// or `SizedBox.shrink()`). If possible, provide children with non-zero size,
|
||||
/// return null from [builder], or set a [childCount].
|
||||
final int? childCount;
|
||||
|
||||
/// {@template flutter.widgets.SliverChildBuilderDelegate.addAutomaticKeepAlives}
|
||||
/// Whether to wrap each child in an [AutomaticKeepAlive].
|
||||
///
|
||||
/// Typically, children in lazy list are wrapped in [AutomaticKeepAlive]
|
||||
/// widgets so that children can use [KeepAliveNotification]s to preserve
|
||||
/// their state when they would otherwise be garbage collected off-screen.
|
||||
///
|
||||
/// This feature (and [addRepaintBoundaries]) must be disabled if the children
|
||||
/// are going to manually maintain their [KeepAlive] state. It may also be
|
||||
/// more efficient to disable this feature if it is known ahead of time that
|
||||
/// none of the children will ever try to keep themselves alive.
|
||||
///
|
||||
/// Defaults to true.
|
||||
/// {@endtemplate}
|
||||
final bool addAutomaticKeepAlives;
|
||||
|
||||
/// {@template flutter.widgets.SliverChildBuilderDelegate.addRepaintBoundaries}
|
||||
/// Whether to wrap each child in a [RepaintBoundary].
|
||||
///
|
||||
/// Typically, children in a scrolling container are wrapped in repaint
|
||||
/// boundaries so that they do not need to be repainted as the list scrolls.
|
||||
/// If the children are easy to repaint (e.g., solid color blocks or a short
|
||||
/// snippet of text), it might be more efficient to not add a repaint boundary
|
||||
/// and instead always repaint the children during scrolling.
|
||||
///
|
||||
/// Defaults to true.
|
||||
/// {@endtemplate}
|
||||
final bool addRepaintBoundaries;
|
||||
|
||||
/// {@template flutter.widgets.SliverChildBuilderDelegate.addSemanticIndexes}
|
||||
/// Whether to wrap each child in an [IndexedSemantics].
|
||||
///
|
||||
/// Typically, children in a scrolling container must be annotated with a
|
||||
/// semantic index in order to generate the correct accessibility
|
||||
/// announcements. This should only be set to false if the indexes have
|
||||
/// already been provided by an [IndexedSemantics] widget.
|
||||
///
|
||||
/// Defaults to true.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [IndexedSemantics], for an explanation of how to manually
|
||||
/// provide semantic indexes.
|
||||
/// {@endtemplate}
|
||||
final bool addSemanticIndexes;
|
||||
|
||||
/// {@template flutter.widgets.SliverChildBuilderDelegate.semanticIndexOffset}
|
||||
/// An initial offset to add to the semantic indexes generated by this widget.
|
||||
///
|
||||
/// Defaults to zero.
|
||||
/// {@endtemplate}
|
||||
final int semanticIndexOffset;
|
||||
|
||||
/// {@template flutter.widgets.SliverChildBuilderDelegate.semanticIndexCallback}
|
||||
/// A [SemanticIndexCallback] which is used when [addSemanticIndexes] is true.
|
||||
///
|
||||
/// Defaults to providing an index for each widget.
|
||||
/// {@endtemplate}
|
||||
final SemanticIndexCallback semanticIndexCallback;
|
||||
|
||||
/// {@template flutter.widgets.SliverChildBuilderDelegate.findChildIndexCallback}
|
||||
/// Called to find the new index of a child based on its key in case of reordering.
|
||||
///
|
||||
/// If not provided, a child widget may not map to its existing [RenderObject]
|
||||
/// when the order of children returned from the children builder changes.
|
||||
/// This may result in state-loss.
|
||||
///
|
||||
/// This callback should take an input [Key], and it should return the
|
||||
/// index of the child element with that associated key, or null if not found.
|
||||
/// {@endtemplate}
|
||||
final ChildIndexGetter? findChildIndexCallback;
|
||||
|
||||
@override
|
||||
int? findIndexByKey(Key key) {
|
||||
if (findChildIndexCallback == null) {
|
||||
return null;
|
||||
}
|
||||
final Key childKey;
|
||||
if (key is _SaltedValueKey) {
|
||||
final _SaltedValueKey saltedValueKey = key;
|
||||
childKey = saltedValueKey.value;
|
||||
} else {
|
||||
childKey = key;
|
||||
}
|
||||
return findChildIndexCallback!(childKey);
|
||||
}
|
||||
|
||||
@override
|
||||
@pragma('vm:notify-debugger-on-exception')
|
||||
Widget? build(BuildContext context, int index) {
|
||||
if (index < 0 || (childCount != null && index >= childCount!)) {
|
||||
return null;
|
||||
}
|
||||
Widget? child;
|
||||
try {
|
||||
child = builder(context, index);
|
||||
} catch (exception, stackTrace) {
|
||||
child = _createErrorWidget(exception, stackTrace);
|
||||
}
|
||||
if (child == null) {
|
||||
return null;
|
||||
}
|
||||
final Key? key = child.key != null ? _SaltedValueKey(child.key!) : null;
|
||||
if (addRepaintBoundaries) {
|
||||
child = RepaintBoundary(child: child);
|
||||
}
|
||||
if (addSemanticIndexes) {
|
||||
final int? semanticIndex = semanticIndexCallback(child, index);
|
||||
if (semanticIndex != null) {
|
||||
child = IndexedSemantics(index: semanticIndex + semanticIndexOffset, child: child);
|
||||
}
|
||||
}
|
||||
if (addAutomaticKeepAlives) {
|
||||
child = AutomaticKeepAlive(child: _SelectionKeepAlive(child: child));
|
||||
}
|
||||
return KeyedSubtree(key: key, child: child);
|
||||
}
|
||||
|
||||
@override
|
||||
int? get estimatedChildCount => childCount;
|
||||
|
||||
@override
|
||||
bool shouldRebuild(covariant SliverChildBuilderDelegate oldDelegate) => true;
|
||||
}
|
||||
|
||||
/// A delegate that supplies children for slivers using an explicit list.
|
||||
///
|
||||
/// Many slivers lazily construct their box children to avoid creating more
|
||||
/// children than are visible through the [Viewport]. This delegate provides
|
||||
/// children using an explicit list, which is convenient but reduces the benefit
|
||||
/// of building children lazily.
|
||||
///
|
||||
/// In general building all the widgets in advance is not efficient. It is
|
||||
/// better to create a delegate that builds them on demand using
|
||||
/// [SliverChildBuilderDelegate] or by subclassing [SliverChildDelegate]
|
||||
/// directly.
|
||||
///
|
||||
/// This class is provided for the cases where either the list of children is
|
||||
/// known well in advance (ideally the children are themselves compile-time
|
||||
/// constants, for example), and therefore will not be built each time the
|
||||
/// delegate itself is created, or the list is small, such that it's likely
|
||||
/// always visible (and thus there is nothing to be gained by building it on
|
||||
/// demand). For example, the body of a dialog box might fit both of these
|
||||
/// conditions.
|
||||
///
|
||||
/// The widgets in the given [children] list are automatically wrapped in
|
||||
/// [AutomaticKeepAlive] widgets if [addAutomaticKeepAlives] is true (the
|
||||
/// default) and in [RepaintBoundary] widgets if [addRepaintBoundaries] is true
|
||||
/// (also the default).
|
||||
///
|
||||
/// ## Accessibility
|
||||
///
|
||||
/// The [CustomScrollView] requires that its semantic children are annotated
|
||||
/// using [IndexedSemantics]. This is done by default in the delegate with
|
||||
/// the `addSemanticIndexes` parameter set to true.
|
||||
///
|
||||
/// If multiple delegates are used in a single scroll view, then the indexes
|
||||
/// will not be correct by default. The `semanticIndexOffset` can be used to
|
||||
/// offset the semantic indexes of each delegate so that the indexes are
|
||||
/// monotonically increasing. For example, if a scroll view contains two
|
||||
/// delegates where the first has 10 children contributing semantics, then the
|
||||
/// second delegate should offset its children by 10.
|
||||
///
|
||||
/// In certain cases, only a subset of child widgets should be annotated
|
||||
/// with a semantic index. For example, in [ListView.separated()] the
|
||||
/// separators do not have an index associated with them. This is done by
|
||||
/// providing a `semanticIndexCallback` which returns null for separators
|
||||
/// indexes and rounds the non-separator indexes down by half.
|
||||
///
|
||||
/// See [SliverChildBuilderDelegate] for sample code using
|
||||
/// `semanticIndexOffset` and `semanticIndexCallback`.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [SliverChildBuilderDelegate], which is a delegate that uses a builder
|
||||
/// callback to construct the children.
|
||||
class SliverChildListDelegate extends SliverChildDelegate {
|
||||
/// Creates a delegate that supplies children for slivers using the given
|
||||
/// list.
|
||||
///
|
||||
/// The [children], [addAutomaticKeepAlives], [addRepaintBoundaries],
|
||||
/// [addSemanticIndexes], and [semanticIndexCallback] arguments must not be
|
||||
/// null.
|
||||
///
|
||||
/// If the order of children never changes, consider using the constant
|
||||
/// [SliverChildListDelegate.fixed] constructor.
|
||||
SliverChildListDelegate(
|
||||
this.children, {
|
||||
this.addAutomaticKeepAlives = true,
|
||||
this.addRepaintBoundaries = true,
|
||||
this.addSemanticIndexes = true,
|
||||
this.semanticIndexCallback = _kDefaultSemanticIndexCallback,
|
||||
this.semanticIndexOffset = 0,
|
||||
}) : _keyToIndex = <Key?, int>{null: 0};
|
||||
|
||||
/// Creates a constant version of the delegate that supplies children for
|
||||
/// slivers using the given list.
|
||||
///
|
||||
/// If the order of the children will change, consider using the regular
|
||||
/// [SliverChildListDelegate] constructor.
|
||||
///
|
||||
/// The [children], [addAutomaticKeepAlives], [addRepaintBoundaries],
|
||||
/// [addSemanticIndexes], and [semanticIndexCallback] arguments must not be
|
||||
/// null.
|
||||
const SliverChildListDelegate.fixed(
|
||||
this.children, {
|
||||
this.addAutomaticKeepAlives = true,
|
||||
this.addRepaintBoundaries = true,
|
||||
this.addSemanticIndexes = true,
|
||||
this.semanticIndexCallback = _kDefaultSemanticIndexCallback,
|
||||
this.semanticIndexOffset = 0,
|
||||
}) : _keyToIndex = null;
|
||||
|
||||
/// {@macro flutter.widgets.SliverChildBuilderDelegate.addAutomaticKeepAlives}
|
||||
final bool addAutomaticKeepAlives;
|
||||
|
||||
/// {@macro flutter.widgets.SliverChildBuilderDelegate.addRepaintBoundaries}
|
||||
final bool addRepaintBoundaries;
|
||||
|
||||
/// {@macro flutter.widgets.SliverChildBuilderDelegate.addSemanticIndexes}
|
||||
final bool addSemanticIndexes;
|
||||
|
||||
/// {@macro flutter.widgets.SliverChildBuilderDelegate.semanticIndexOffset}
|
||||
final int semanticIndexOffset;
|
||||
|
||||
/// {@macro flutter.widgets.SliverChildBuilderDelegate.semanticIndexCallback}
|
||||
final SemanticIndexCallback semanticIndexCallback;
|
||||
|
||||
/// The widgets to display.
|
||||
///
|
||||
/// If this list is going to be mutated, it is usually wise to put a [Key] on
|
||||
/// each of the child widgets, so that the framework can match old
|
||||
/// configurations to new configurations and maintain the underlying render
|
||||
/// objects.
|
||||
///
|
||||
/// Also, a [Widget] in Flutter is immutable, so directly modifying the
|
||||
/// [children] such as `someWidget.children.add(...)` or
|
||||
/// passing a reference of the original list value to the [children] parameter
|
||||
/// will result in incorrect behaviors. Whenever the
|
||||
/// children list is modified, a new list object should be provided.
|
||||
///
|
||||
/// The following code corrects the problem mentioned above.
|
||||
///
|
||||
/// ```dart
|
||||
/// class SomeWidgetState extends State<SomeWidget> {
|
||||
/// final List<Widget> _children = <Widget>[];
|
||||
///
|
||||
/// void someHandler() {
|
||||
/// setState(() {
|
||||
/// // The key here allows Flutter to reuse the underlying render
|
||||
/// // objects even if the children list is recreated.
|
||||
/// _children.add(ChildWidget(key: UniqueKey()));
|
||||
/// });
|
||||
/// }
|
||||
///
|
||||
/// @override
|
||||
/// Widget build(BuildContext context) {
|
||||
/// // Always create a new list of children as a Widget is immutable.
|
||||
/// return PageView(children: List<Widget>.of(_children));
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
final List<Widget> children;
|
||||
|
||||
/// A map to cache key to index lookup for children.
|
||||
///
|
||||
/// _keyToIndex[null] is used as current index during the lazy loading process
|
||||
/// in [_findChildIndex]. _keyToIndex should never be used for looking up null key.
|
||||
final Map<Key?, int>? _keyToIndex;
|
||||
|
||||
bool get _isConstantInstance => _keyToIndex == null;
|
||||
|
||||
int? _findChildIndex(Key key) {
|
||||
if (_isConstantInstance) {
|
||||
return null;
|
||||
}
|
||||
// Lazily fill the [_keyToIndex].
|
||||
if (!_keyToIndex!.containsKey(key)) {
|
||||
int index = _keyToIndex![null]!;
|
||||
while (index < children.length) {
|
||||
final Widget child = children[index];
|
||||
if (child.key != null) {
|
||||
_keyToIndex![child.key] = index;
|
||||
}
|
||||
if (child.key == key) {
|
||||
// Record current index for next function call.
|
||||
_keyToIndex![null] = index + 1;
|
||||
return index;
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
_keyToIndex![null] = index;
|
||||
} else {
|
||||
return _keyToIndex![key];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
int? findIndexByKey(Key key) {
|
||||
final Key childKey;
|
||||
if (key is _SaltedValueKey) {
|
||||
final _SaltedValueKey saltedValueKey = key;
|
||||
childKey = saltedValueKey.value;
|
||||
} else {
|
||||
childKey = key;
|
||||
}
|
||||
return _findChildIndex(childKey);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget? build(BuildContext context, int index) {
|
||||
if (index < 0 || index >= children.length) {
|
||||
return null;
|
||||
}
|
||||
Widget child = children[index];
|
||||
final Key? key = child.key != null? _SaltedValueKey(child.key!) : null;
|
||||
if (addRepaintBoundaries) {
|
||||
child = RepaintBoundary(child: child);
|
||||
}
|
||||
if (addSemanticIndexes) {
|
||||
final int? semanticIndex = semanticIndexCallback(child, index);
|
||||
if (semanticIndex != null) {
|
||||
child = IndexedSemantics(index: semanticIndex + semanticIndexOffset, child: child);
|
||||
}
|
||||
}
|
||||
if (addAutomaticKeepAlives) {
|
||||
child = AutomaticKeepAlive(child: _SelectionKeepAlive(child: child));
|
||||
}
|
||||
|
||||
return KeyedSubtree(key: key, child: child);
|
||||
}
|
||||
|
||||
@override
|
||||
int? get estimatedChildCount => children.length;
|
||||
|
||||
@override
|
||||
bool shouldRebuild(covariant SliverChildListDelegate oldDelegate) {
|
||||
return children != oldDelegate.children;
|
||||
}
|
||||
}
|
||||
|
||||
class _SelectionKeepAlive extends StatefulWidget {
|
||||
/// Creates a widget that listens to [KeepAliveNotification]s and maintains a
|
||||
/// [KeepAlive] widget appropriately.
|
||||
const _SelectionKeepAlive({
|
||||
required this.child,
|
||||
});
|
||||
|
||||
/// The widget below this widget in the tree.
|
||||
///
|
||||
/// {@macro flutter.widgets.ProxyWidget.child}
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
State<_SelectionKeepAlive> createState() => _SelectionKeepAliveState();
|
||||
}
|
||||
|
||||
class _SelectionKeepAliveState extends State<_SelectionKeepAlive> with AutomaticKeepAliveClientMixin implements SelectionRegistrar {
|
||||
Set<Selectable>? _selectablesWithSelections;
|
||||
Map<Selectable, VoidCallback>? _selectableAttachments;
|
||||
SelectionRegistrar? _registrar;
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => _wantKeepAlive;
|
||||
bool _wantKeepAlive = false;
|
||||
set wantKeepAlive(bool value) {
|
||||
if (_wantKeepAlive != value) {
|
||||
_wantKeepAlive = value;
|
||||
updateKeepAlive();
|
||||
}
|
||||
}
|
||||
|
||||
VoidCallback listensTo(Selectable selectable) {
|
||||
return () {
|
||||
if (selectable.value.hasSelection) {
|
||||
_updateSelectablesWithSelections(selectable, add: true);
|
||||
} else {
|
||||
_updateSelectablesWithSelections(selectable, add: false);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void _updateSelectablesWithSelections(Selectable selectable, {required bool add}) {
|
||||
if (add) {
|
||||
assert(selectable.value.hasSelection);
|
||||
_selectablesWithSelections ??= <Selectable>{};
|
||||
_selectablesWithSelections!.add(selectable);
|
||||
} else {
|
||||
_selectablesWithSelections?.remove(selectable);
|
||||
}
|
||||
wantKeepAlive = _selectablesWithSelections?.isNotEmpty ?? false;
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
final SelectionRegistrar? newRegistrar = SelectionContainer.maybeOf(context);
|
||||
if (_registrar != newRegistrar) {
|
||||
if (_registrar != null) {
|
||||
_selectableAttachments?.keys.forEach(_registrar!.remove);
|
||||
}
|
||||
_registrar = newRegistrar;
|
||||
if (_registrar != null) {
|
||||
_selectableAttachments?.keys.forEach(_registrar!.add);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void add(Selectable selectable) {
|
||||
final VoidCallback attachment = listensTo(selectable);
|
||||
selectable.addListener(attachment);
|
||||
_selectableAttachments ??= <Selectable, VoidCallback>{};
|
||||
_selectableAttachments![selectable] = attachment;
|
||||
_registrar!.add(selectable);
|
||||
if (selectable.value.hasSelection) {
|
||||
_updateSelectablesWithSelections(selectable, add: true);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void remove(Selectable selectable) {
|
||||
if (_selectableAttachments == null) {
|
||||
return;
|
||||
}
|
||||
assert(_selectableAttachments!.containsKey(selectable));
|
||||
final VoidCallback attachment = _selectableAttachments!.remove(selectable)!;
|
||||
selectable.removeListener(attachment);
|
||||
_registrar!.remove(selectable);
|
||||
_updateSelectablesWithSelections(selectable, add: false);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (_selectableAttachments != null) {
|
||||
for (final Selectable selectable in _selectableAttachments!.keys) {
|
||||
_registrar!.remove(selectable);
|
||||
selectable.removeListener(_selectableAttachments![selectable]!);
|
||||
}
|
||||
_selectableAttachments = null;
|
||||
}
|
||||
_selectablesWithSelections = null;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
if (_registrar == null) {
|
||||
return widget.child;
|
||||
}
|
||||
return SelectionRegistrarScope(
|
||||
registrar: this,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Return a Widget for the given Exception
|
||||
Widget _createErrorWidget(Object exception, StackTrace stackTrace) {
|
||||
final FlutterErrorDetails details = FlutterErrorDetails(
|
||||
exception: exception,
|
||||
stack: stackTrace,
|
||||
library: 'widgets library',
|
||||
context: ErrorDescription('building'),
|
||||
);
|
||||
FlutterError.reportError(details);
|
||||
return ErrorWidget.builder(details);
|
||||
}
|
@ -17,6 +17,7 @@ import 'notification_listener.dart';
|
||||
import 'primary_scroll_controller.dart';
|
||||
import 'scroll_configuration.dart';
|
||||
import 'scroll_controller.dart';
|
||||
import 'scroll_delegate.dart';
|
||||
import 'scroll_notification.dart';
|
||||
import 'scroll_physics.dart';
|
||||
import 'scrollable.dart';
|
||||
|
@ -11,873 +11,9 @@ import 'package:flutter/rendering.dart';
|
||||
import 'automatic_keep_alive.dart';
|
||||
import 'basic.dart';
|
||||
import 'framework.dart';
|
||||
import 'selection_container.dart';
|
||||
import 'scroll_delegate.dart';
|
||||
|
||||
export 'package:flutter/rendering.dart' show
|
||||
SliverGridDelegate,
|
||||
SliverGridDelegateWithFixedCrossAxisCount,
|
||||
SliverGridDelegateWithMaxCrossAxisExtent;
|
||||
|
||||
// Examples can assume:
|
||||
// late SliverGridDelegateWithMaxCrossAxisExtent _gridDelegate;
|
||||
// abstract class SomeWidget extends StatefulWidget { const SomeWidget({super.key}); }
|
||||
// typedef ChildWidget = Placeholder;
|
||||
|
||||
/// A callback which produces a semantic index given a widget and the local index.
|
||||
///
|
||||
/// Return a null value to prevent a widget from receiving an index.
|
||||
///
|
||||
/// A semantic index is used to tag child semantic nodes for accessibility
|
||||
/// announcements in scroll view.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [CustomScrollView], for an explanation of scroll semantics.
|
||||
/// * [SliverChildBuilderDelegate], for an explanation of how this is used to
|
||||
/// generate indexes.
|
||||
typedef SemanticIndexCallback = int? Function(Widget widget, int localIndex);
|
||||
|
||||
int _kDefaultSemanticIndexCallback(Widget _, int localIndex) => localIndex;
|
||||
|
||||
/// A delegate that supplies children for slivers.
|
||||
///
|
||||
/// Many slivers lazily construct their box children to avoid creating more
|
||||
/// children than are visible through the [Viewport]. Rather than receiving
|
||||
/// their children as an explicit [List], they receive their children using a
|
||||
/// [SliverChildDelegate].
|
||||
///
|
||||
/// It's uncommon to subclass [SliverChildDelegate]. Instead, consider using one
|
||||
/// of the existing subclasses that provide adaptors to builder callbacks or
|
||||
/// explicit child lists.
|
||||
///
|
||||
/// {@template flutter.widgets.SliverChildDelegate.lifecycle}
|
||||
/// ## Child elements' lifecycle
|
||||
///
|
||||
/// ### Creation
|
||||
///
|
||||
/// While laying out the list, visible children's elements, states and render
|
||||
/// objects will be created lazily based on existing widgets (such as in the
|
||||
/// case of [SliverChildListDelegate]) or lazily provided ones (such as in the
|
||||
/// case of [SliverChildBuilderDelegate]).
|
||||
///
|
||||
/// ### Destruction
|
||||
///
|
||||
/// When a child is scrolled out of view, the associated element subtree, states
|
||||
/// and render objects are destroyed. A new child at the same position in the
|
||||
/// sliver will be lazily recreated along with new elements, states and render
|
||||
/// objects when it is scrolled back.
|
||||
///
|
||||
/// ### Destruction mitigation
|
||||
///
|
||||
/// In order to preserve state as child elements are scrolled in and out of
|
||||
/// view, the following options are possible:
|
||||
///
|
||||
/// * Moving the ownership of non-trivial UI-state-driving business logic
|
||||
/// out of the sliver child subtree. For instance, if a list contains posts
|
||||
/// with their number of upvotes coming from a cached network response, store
|
||||
/// the list of posts and upvote number in a data model outside the list. Let
|
||||
/// the sliver child UI subtree be easily recreate-able from the
|
||||
/// source-of-truth model object. Use [StatefulWidget]s in the child widget
|
||||
/// subtree to store instantaneous UI state only.
|
||||
///
|
||||
/// * Letting [KeepAlive] be the root widget of the sliver child widget subtree
|
||||
/// that needs to be preserved. The [KeepAlive] widget marks the child
|
||||
/// subtree's top render object child for keepalive. When the associated top
|
||||
/// render object is scrolled out of view, the sliver keeps the child's
|
||||
/// render object (and by extension, its associated elements and states) in a
|
||||
/// cache list instead of destroying them. When scrolled back into view, the
|
||||
/// render object is repainted as-is (if it wasn't marked dirty in the
|
||||
/// interim).
|
||||
///
|
||||
/// This only works if the [SliverChildDelegate] subclasses don't wrap the
|
||||
/// child widget subtree with other widgets such as [AutomaticKeepAlive] and
|
||||
/// [RepaintBoundary] via `addAutomaticKeepAlives` and
|
||||
/// `addRepaintBoundaries`.
|
||||
///
|
||||
/// * Using [AutomaticKeepAlive] widgets (inserted by default in
|
||||
/// [SliverChildListDelegate] or [SliverChildListDelegate]).
|
||||
/// [AutomaticKeepAlive] allows descendant widgets to control whether the
|
||||
/// subtree is actually kept alive or not. This behavior is in contrast with
|
||||
/// [KeepAlive], which will unconditionally keep the subtree alive.
|
||||
///
|
||||
/// As an example, the [EditableText] widget signals its sliver child element
|
||||
/// subtree to stay alive while its text field has input focus. If it doesn't
|
||||
/// have focus and no other descendants signaled for keepalive via a
|
||||
/// [KeepAliveNotification], the sliver child element subtree will be
|
||||
/// destroyed when scrolled away.
|
||||
///
|
||||
/// [AutomaticKeepAlive] descendants typically signal it to be kept alive by
|
||||
/// using the [AutomaticKeepAliveClientMixin], then implementing the
|
||||
/// [AutomaticKeepAliveClientMixin.wantKeepAlive] getter and calling
|
||||
/// [AutomaticKeepAliveClientMixin.updateKeepAlive].
|
||||
///
|
||||
/// ## Using more than one delegate in a [Viewport]
|
||||
///
|
||||
/// If multiple delegates are used in a single scroll view, the first child of
|
||||
/// each delegate will always be laid out, even if it extends beyond the
|
||||
/// currently viewable area. This is because at least one child is required in
|
||||
/// order to [estimateMaxScrollOffset] for the whole scroll view, as it uses the
|
||||
/// currently built children to estimate the remaining children's extent.
|
||||
/// {@endtemplate}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [SliverChildBuilderDelegate], which is a delegate that uses a builder
|
||||
/// callback to construct the children.
|
||||
/// * [SliverChildListDelegate], which is a delegate that has an explicit list
|
||||
/// of children.
|
||||
abstract class SliverChildDelegate {
|
||||
/// Abstract const constructor. This constructor enables subclasses to provide
|
||||
/// const constructors so that they can be used in const expressions.
|
||||
const SliverChildDelegate();
|
||||
|
||||
/// Returns the child with the given index.
|
||||
///
|
||||
/// Should return null if asked to build a widget with a greater
|
||||
/// index than exists. If this returns null, [estimatedChildCount]
|
||||
/// must subsequently return a precise non-null value (which is then
|
||||
/// used to implement [RenderSliverBoxChildManager.childCount]).
|
||||
///
|
||||
/// Subclasses typically override this function and wrap their children in
|
||||
/// [AutomaticKeepAlive], [IndexedSemantics], and [RepaintBoundary] widgets.
|
||||
///
|
||||
/// The values returned by this method are cached. To indicate that the
|
||||
/// widgets have changed, a new delegate must be provided, and the new
|
||||
/// delegate's [shouldRebuild] method must return true.
|
||||
Widget? build(BuildContext context, int index);
|
||||
|
||||
/// Returns an estimate of the number of children this delegate will build.
|
||||
///
|
||||
/// Used to estimate the maximum scroll offset if [estimateMaxScrollOffset]
|
||||
/// returns null.
|
||||
///
|
||||
/// Return null if there are an unbounded number of children or if it would
|
||||
/// be too difficult to estimate the number of children.
|
||||
///
|
||||
/// This must return a precise number once [build] has returned null, as it
|
||||
/// used to implement [RenderSliverBoxChildManager.childCount].
|
||||
int? get estimatedChildCount => null;
|
||||
|
||||
/// Returns an estimate of the max scroll extent for all the children.
|
||||
///
|
||||
/// Subclasses should override this function if they have additional
|
||||
/// information about their max scroll extent.
|
||||
///
|
||||
/// The default implementation returns null, which causes the caller to
|
||||
/// extrapolate the max scroll offset from the given parameters.
|
||||
double? estimateMaxScrollOffset(
|
||||
int firstIndex,
|
||||
int lastIndex,
|
||||
double leadingScrollOffset,
|
||||
double trailingScrollOffset,
|
||||
) => null;
|
||||
|
||||
/// Called at the end of layout to indicate that layout is now complete.
|
||||
///
|
||||
/// The `firstIndex` argument is the index of the first child that was
|
||||
/// included in the current layout. The `lastIndex` argument is the index of
|
||||
/// the last child that was included in the current layout.
|
||||
///
|
||||
/// Useful for subclasses that which to track which children are included in
|
||||
/// the underlying render tree.
|
||||
void didFinishLayout(int firstIndex, int lastIndex) { }
|
||||
|
||||
/// Called whenever a new instance of the child delegate class is
|
||||
/// provided to the sliver.
|
||||
///
|
||||
/// If the new instance represents different information than the old
|
||||
/// instance, then the method should return true, otherwise it should return
|
||||
/// false.
|
||||
///
|
||||
/// If the method returns false, then the [build] call might be optimized
|
||||
/// away.
|
||||
bool shouldRebuild(covariant SliverChildDelegate oldDelegate);
|
||||
|
||||
/// Find index of child element with associated key.
|
||||
///
|
||||
/// This will be called during `performRebuild` in [SliverMultiBoxAdaptorElement]
|
||||
/// to check if a child has moved to a different position. It should return the
|
||||
/// index of the child element with associated key, null if not found.
|
||||
///
|
||||
/// If not provided, a child widget may not map to its existing [RenderObject]
|
||||
/// when the order of children returned from the children builder changes.
|
||||
/// This may result in state-loss.
|
||||
int? findIndexByKey(Key key) => null;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
final List<String> description = <String>[];
|
||||
debugFillDescription(description);
|
||||
return '${describeIdentity(this)}(${description.join(", ")})';
|
||||
}
|
||||
|
||||
/// Add additional information to the given description for use by [toString].
|
||||
@protected
|
||||
@mustCallSuper
|
||||
void debugFillDescription(List<String> description) {
|
||||
try {
|
||||
final int? children = estimatedChildCount;
|
||||
if (children != null) {
|
||||
description.add('estimated child count: $children');
|
||||
}
|
||||
} catch (e) {
|
||||
// The exception is forwarded to widget inspector.
|
||||
description.add('estimated child count: EXCEPTION (${e.runtimeType})');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _SaltedValueKey extends ValueKey<Key> {
|
||||
const _SaltedValueKey(super.key);
|
||||
}
|
||||
|
||||
/// Called to find the new index of a child based on its `key` in case of
|
||||
/// reordering.
|
||||
///
|
||||
/// If the child with the `key` is no longer present, null is returned.
|
||||
///
|
||||
/// Used by [SliverChildBuilderDelegate.findChildIndexCallback].
|
||||
typedef ChildIndexGetter = int? Function(Key key);
|
||||
|
||||
/// A delegate that supplies children for slivers using a builder callback.
|
||||
///
|
||||
/// Many slivers lazily construct their box children to avoid creating more
|
||||
/// children than are visible through the [Viewport]. This delegate provides
|
||||
/// children using a [NullableIndexedWidgetBuilder] callback, so that the children do
|
||||
/// not even have to be built until they are displayed.
|
||||
///
|
||||
/// The widgets returned from the builder callback are automatically wrapped in
|
||||
/// [AutomaticKeepAlive] widgets if [addAutomaticKeepAlives] is true (the
|
||||
/// default) and in [RepaintBoundary] widgets if [addRepaintBoundaries] is true
|
||||
/// (also the default).
|
||||
///
|
||||
/// ## Accessibility
|
||||
///
|
||||
/// The [CustomScrollView] requires that its semantic children are annotated
|
||||
/// using [IndexedSemantics]. This is done by default in the delegate with
|
||||
/// the `addSemanticIndexes` parameter set to true.
|
||||
///
|
||||
/// If multiple delegates are used in a single scroll view, then the indexes
|
||||
/// will not be correct by default. The `semanticIndexOffset` can be used to
|
||||
/// offset the semantic indexes of each delegate so that the indexes are
|
||||
/// monotonically increasing. For example, if a scroll view contains two
|
||||
/// delegates where the first has 10 children contributing semantics, then the
|
||||
/// second delegate should offset its children by 10.
|
||||
///
|
||||
/// {@tool snippet}
|
||||
///
|
||||
/// This sample code shows how to use `semanticIndexOffset` to handle multiple
|
||||
/// delegates in a single scroll view.
|
||||
///
|
||||
/// ```dart
|
||||
/// CustomScrollView(
|
||||
/// semanticChildCount: 4,
|
||||
/// slivers: <Widget>[
|
||||
/// SliverGrid(
|
||||
/// gridDelegate: _gridDelegate,
|
||||
/// delegate: SliverChildBuilderDelegate(
|
||||
/// (BuildContext context, int index) {
|
||||
/// return const Text('...');
|
||||
/// },
|
||||
/// childCount: 2,
|
||||
/// ),
|
||||
/// ),
|
||||
/// SliverGrid(
|
||||
/// gridDelegate: _gridDelegate,
|
||||
/// delegate: SliverChildBuilderDelegate(
|
||||
/// (BuildContext context, int index) {
|
||||
/// return const Text('...');
|
||||
/// },
|
||||
/// childCount: 2,
|
||||
/// semanticIndexOffset: 2,
|
||||
/// ),
|
||||
/// ),
|
||||
/// ],
|
||||
/// )
|
||||
/// ```
|
||||
/// {@end-tool}
|
||||
///
|
||||
/// In certain cases, only a subset of child widgets should be annotated
|
||||
/// with a semantic index. For example, in [ListView.separated()] the
|
||||
/// separators do not have an index associated with them. This is done by
|
||||
/// providing a `semanticIndexCallback` which returns null for separators
|
||||
/// indexes and rounds the non-separator indexes down by half.
|
||||
///
|
||||
/// {@tool snippet}
|
||||
///
|
||||
/// This sample code shows how to use `semanticIndexCallback` to handle
|
||||
/// annotating a subset of child nodes with a semantic index. There is
|
||||
/// a [Spacer] widget at odd indexes which should not have a semantic
|
||||
/// index.
|
||||
///
|
||||
/// ```dart
|
||||
/// CustomScrollView(
|
||||
/// semanticChildCount: 5,
|
||||
/// slivers: <Widget>[
|
||||
/// SliverGrid(
|
||||
/// gridDelegate: _gridDelegate,
|
||||
/// delegate: SliverChildBuilderDelegate(
|
||||
/// (BuildContext context, int index) {
|
||||
/// if (index.isEven) {
|
||||
/// return const Text('...');
|
||||
/// }
|
||||
/// return const Spacer();
|
||||
/// },
|
||||
/// semanticIndexCallback: (Widget widget, int localIndex) {
|
||||
/// if (localIndex.isEven) {
|
||||
/// return localIndex ~/ 2;
|
||||
/// }
|
||||
/// return null;
|
||||
/// },
|
||||
/// childCount: 10,
|
||||
/// ),
|
||||
/// ),
|
||||
/// ],
|
||||
/// )
|
||||
/// ```
|
||||
/// {@end-tool}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [SliverChildListDelegate], which is a delegate that has an explicit list
|
||||
/// of children.
|
||||
/// * [IndexedSemantics], for an example of manually annotating child nodes
|
||||
/// with semantic indexes.
|
||||
class SliverChildBuilderDelegate extends SliverChildDelegate {
|
||||
/// Creates a delegate that supplies children for slivers using the given
|
||||
/// builder callback.
|
||||
///
|
||||
/// The [builder], [addAutomaticKeepAlives], [addRepaintBoundaries],
|
||||
/// [addSemanticIndexes], and [semanticIndexCallback] arguments must not be
|
||||
/// null.
|
||||
///
|
||||
/// If the order in which [builder] returns children ever changes, consider
|
||||
/// providing a [findChildIndexCallback]. This allows the delegate to find the
|
||||
/// new index for a child that was previously located at a different index to
|
||||
/// attach the existing state to the [Widget] at its new location.
|
||||
const SliverChildBuilderDelegate(
|
||||
this.builder, {
|
||||
this.findChildIndexCallback,
|
||||
this.childCount,
|
||||
this.addAutomaticKeepAlives = true,
|
||||
this.addRepaintBoundaries = true,
|
||||
this.addSemanticIndexes = true,
|
||||
this.semanticIndexCallback = _kDefaultSemanticIndexCallback,
|
||||
this.semanticIndexOffset = 0,
|
||||
});
|
||||
|
||||
/// Called to build children for the sliver.
|
||||
///
|
||||
/// Will be called only for indices greater than or equal to zero and less
|
||||
/// than [childCount] (if [childCount] is non-null).
|
||||
///
|
||||
/// Should return null if asked to build a widget with a greater index than
|
||||
/// exists.
|
||||
///
|
||||
/// May result in an infinite loop or run out of memory if [childCount] is null
|
||||
/// and the [builder] always provides a zero-size widget (such as `Container()`
|
||||
/// or `SizedBox.shrink()`). If possible, provide children with non-zero size,
|
||||
/// return null from [builder], or set a [childCount].
|
||||
///
|
||||
/// The delegate wraps the children returned by this builder in
|
||||
/// [RepaintBoundary] widgets.
|
||||
final NullableIndexedWidgetBuilder builder;
|
||||
|
||||
/// The total number of children this delegate can provide.
|
||||
///
|
||||
/// If null, the number of children is determined by the least index for which
|
||||
/// [builder] returns null.
|
||||
///
|
||||
/// May result in an infinite loop or run out of memory if [childCount] is null
|
||||
/// and the [builder] always provides a zero-size widget (such as `Container()`
|
||||
/// or `SizedBox.shrink()`). If possible, provide children with non-zero size,
|
||||
/// return null from [builder], or set a [childCount].
|
||||
final int? childCount;
|
||||
|
||||
/// Whether to wrap each child in an [AutomaticKeepAlive].
|
||||
///
|
||||
/// Typically, children in lazy list are wrapped in [AutomaticKeepAlive]
|
||||
/// widgets so that children can use [KeepAliveNotification]s to preserve
|
||||
/// their state when they would otherwise be garbage collected off-screen.
|
||||
///
|
||||
/// This feature (and [addRepaintBoundaries]) must be disabled if the children
|
||||
/// are going to manually maintain their [KeepAlive] state. It may also be
|
||||
/// more efficient to disable this feature if it is known ahead of time that
|
||||
/// none of the children will ever try to keep themselves alive.
|
||||
///
|
||||
/// Defaults to true.
|
||||
final bool addAutomaticKeepAlives;
|
||||
|
||||
/// Whether to wrap each child in a [RepaintBoundary].
|
||||
///
|
||||
/// Typically, children in a scrolling container are wrapped in repaint
|
||||
/// boundaries so that they do not need to be repainted as the list scrolls.
|
||||
/// If the children are easy to repaint (e.g., solid color blocks or a short
|
||||
/// snippet of text), it might be more efficient to not add a repaint boundary
|
||||
/// and instead always repaint the children during scrolling.
|
||||
///
|
||||
/// Defaults to true.
|
||||
final bool addRepaintBoundaries;
|
||||
|
||||
/// Whether to wrap each child in an [IndexedSemantics].
|
||||
///
|
||||
/// Typically, children in a scrolling container must be annotated with a
|
||||
/// semantic index in order to generate the correct accessibility
|
||||
/// announcements. This should only be set to false if the indexes have
|
||||
/// already been provided by an [IndexedSemantics] widget.
|
||||
///
|
||||
/// Defaults to true.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [IndexedSemantics], for an explanation of how to manually
|
||||
/// provide semantic indexes.
|
||||
final bool addSemanticIndexes;
|
||||
|
||||
/// An initial offset to add to the semantic indexes generated by this widget.
|
||||
///
|
||||
/// Defaults to zero.
|
||||
final int semanticIndexOffset;
|
||||
|
||||
/// A [SemanticIndexCallback] which is used when [addSemanticIndexes] is true.
|
||||
///
|
||||
/// Defaults to providing an index for each widget.
|
||||
final SemanticIndexCallback semanticIndexCallback;
|
||||
|
||||
/// {@template flutter.widgets.SliverChildBuilderDelegate.findChildIndexCallback}
|
||||
/// Called to find the new index of a child based on its key in case of reordering.
|
||||
///
|
||||
/// If not provided, a child widget may not map to its existing [RenderObject]
|
||||
/// when the order of children returned from the children builder changes.
|
||||
/// This may result in state-loss.
|
||||
///
|
||||
/// This callback should take an input [Key], and it should return the
|
||||
/// index of the child element with that associated key, or null if not found.
|
||||
/// {@endtemplate}
|
||||
final ChildIndexGetter? findChildIndexCallback;
|
||||
|
||||
@override
|
||||
int? findIndexByKey(Key key) {
|
||||
if (findChildIndexCallback == null) {
|
||||
return null;
|
||||
}
|
||||
final Key childKey;
|
||||
if (key is _SaltedValueKey) {
|
||||
final _SaltedValueKey saltedValueKey = key;
|
||||
childKey = saltedValueKey.value;
|
||||
} else {
|
||||
childKey = key;
|
||||
}
|
||||
return findChildIndexCallback!(childKey);
|
||||
}
|
||||
|
||||
@override
|
||||
@pragma('vm:notify-debugger-on-exception')
|
||||
Widget? build(BuildContext context, int index) {
|
||||
if (index < 0 || (childCount != null && index >= childCount!)) {
|
||||
return null;
|
||||
}
|
||||
Widget? child;
|
||||
try {
|
||||
child = builder(context, index);
|
||||
} catch (exception, stackTrace) {
|
||||
child = _createErrorWidget(exception, stackTrace);
|
||||
}
|
||||
if (child == null) {
|
||||
return null;
|
||||
}
|
||||
final Key? key = child.key != null ? _SaltedValueKey(child.key!) : null;
|
||||
if (addRepaintBoundaries) {
|
||||
child = RepaintBoundary(child: child);
|
||||
}
|
||||
if (addSemanticIndexes) {
|
||||
final int? semanticIndex = semanticIndexCallback(child, index);
|
||||
if (semanticIndex != null) {
|
||||
child = IndexedSemantics(index: semanticIndex + semanticIndexOffset, child: child);
|
||||
}
|
||||
}
|
||||
if (addAutomaticKeepAlives) {
|
||||
child = AutomaticKeepAlive(child: _SelectionKeepAlive(child: child));
|
||||
}
|
||||
return KeyedSubtree(key: key, child: child);
|
||||
}
|
||||
|
||||
@override
|
||||
int? get estimatedChildCount => childCount;
|
||||
|
||||
@override
|
||||
bool shouldRebuild(covariant SliverChildBuilderDelegate oldDelegate) => true;
|
||||
}
|
||||
|
||||
/// A delegate that supplies children for slivers using an explicit list.
|
||||
///
|
||||
/// Many slivers lazily construct their box children to avoid creating more
|
||||
/// children than are visible through the [Viewport]. This delegate provides
|
||||
/// children using an explicit list, which is convenient but reduces the benefit
|
||||
/// of building children lazily.
|
||||
///
|
||||
/// In general building all the widgets in advance is not efficient. It is
|
||||
/// better to create a delegate that builds them on demand using
|
||||
/// [SliverChildBuilderDelegate] or by subclassing [SliverChildDelegate]
|
||||
/// directly.
|
||||
///
|
||||
/// This class is provided for the cases where either the list of children is
|
||||
/// known well in advance (ideally the children are themselves compile-time
|
||||
/// constants, for example), and therefore will not be built each time the
|
||||
/// delegate itself is created, or the list is small, such that it's likely
|
||||
/// always visible (and thus there is nothing to be gained by building it on
|
||||
/// demand). For example, the body of a dialog box might fit both of these
|
||||
/// conditions.
|
||||
///
|
||||
/// The widgets in the given [children] list are automatically wrapped in
|
||||
/// [AutomaticKeepAlive] widgets if [addAutomaticKeepAlives] is true (the
|
||||
/// default) and in [RepaintBoundary] widgets if [addRepaintBoundaries] is true
|
||||
/// (also the default).
|
||||
///
|
||||
/// ## Accessibility
|
||||
///
|
||||
/// The [CustomScrollView] requires that its semantic children are annotated
|
||||
/// using [IndexedSemantics]. This is done by default in the delegate with
|
||||
/// the `addSemanticIndexes` parameter set to true.
|
||||
///
|
||||
/// If multiple delegates are used in a single scroll view, then the indexes
|
||||
/// will not be correct by default. The `semanticIndexOffset` can be used to
|
||||
/// offset the semantic indexes of each delegate so that the indexes are
|
||||
/// monotonically increasing. For example, if a scroll view contains two
|
||||
/// delegates where the first has 10 children contributing semantics, then the
|
||||
/// second delegate should offset its children by 10.
|
||||
///
|
||||
/// In certain cases, only a subset of child widgets should be annotated
|
||||
/// with a semantic index. For example, in [ListView.separated()] the
|
||||
/// separators do not have an index associated with them. This is done by
|
||||
/// providing a `semanticIndexCallback` which returns null for separators
|
||||
/// indexes and rounds the non-separator indexes down by half.
|
||||
///
|
||||
/// See [SliverChildBuilderDelegate] for sample code using
|
||||
/// `semanticIndexOffset` and `semanticIndexCallback`.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [SliverChildBuilderDelegate], which is a delegate that uses a builder
|
||||
/// callback to construct the children.
|
||||
class SliverChildListDelegate extends SliverChildDelegate {
|
||||
/// Creates a delegate that supplies children for slivers using the given
|
||||
/// list.
|
||||
///
|
||||
/// The [children], [addAutomaticKeepAlives], [addRepaintBoundaries],
|
||||
/// [addSemanticIndexes], and [semanticIndexCallback] arguments must not be
|
||||
/// null.
|
||||
///
|
||||
/// If the order of children never changes, consider using the constant
|
||||
/// [SliverChildListDelegate.fixed] constructor.
|
||||
SliverChildListDelegate(
|
||||
this.children, {
|
||||
this.addAutomaticKeepAlives = true,
|
||||
this.addRepaintBoundaries = true,
|
||||
this.addSemanticIndexes = true,
|
||||
this.semanticIndexCallback = _kDefaultSemanticIndexCallback,
|
||||
this.semanticIndexOffset = 0,
|
||||
}) : _keyToIndex = <Key?, int>{null: 0};
|
||||
|
||||
/// Creates a constant version of the delegate that supplies children for
|
||||
/// slivers using the given list.
|
||||
///
|
||||
/// If the order of the children will change, consider using the regular
|
||||
/// [SliverChildListDelegate] constructor.
|
||||
///
|
||||
/// The [children], [addAutomaticKeepAlives], [addRepaintBoundaries],
|
||||
/// [addSemanticIndexes], and [semanticIndexCallback] arguments must not be
|
||||
/// null.
|
||||
const SliverChildListDelegate.fixed(
|
||||
this.children, {
|
||||
this.addAutomaticKeepAlives = true,
|
||||
this.addRepaintBoundaries = true,
|
||||
this.addSemanticIndexes = true,
|
||||
this.semanticIndexCallback = _kDefaultSemanticIndexCallback,
|
||||
this.semanticIndexOffset = 0,
|
||||
}) : _keyToIndex = null;
|
||||
|
||||
/// Whether to wrap each child in an [AutomaticKeepAlive].
|
||||
///
|
||||
/// Typically, children in lazy list are wrapped in [AutomaticKeepAlive]
|
||||
/// widgets so that children can use [KeepAliveNotification]s to preserve
|
||||
/// their state when they would otherwise be garbage collected off-screen.
|
||||
///
|
||||
/// This feature (and [addRepaintBoundaries]) must be disabled if the children
|
||||
/// are going to manually maintain their [KeepAlive] state. It may also be
|
||||
/// more efficient to disable this feature if it is known ahead of time that
|
||||
/// none of the children will ever try to keep themselves alive.
|
||||
///
|
||||
/// Defaults to true.
|
||||
final bool addAutomaticKeepAlives;
|
||||
|
||||
/// Whether to wrap each child in a [RepaintBoundary].
|
||||
///
|
||||
/// Typically, children in a scrolling container are wrapped in repaint
|
||||
/// boundaries so that they do not need to be repainted as the list scrolls.
|
||||
/// If the children are easy to repaint (e.g., solid color blocks or a short
|
||||
/// snippet of text), it might be more efficient to not add a repaint boundary
|
||||
/// and instead always repaint the children during scrolling.
|
||||
///
|
||||
/// Defaults to true.
|
||||
final bool addRepaintBoundaries;
|
||||
|
||||
/// Whether to wrap each child in an [IndexedSemantics].
|
||||
///
|
||||
/// Typically, children in a scrolling container must be annotated with a
|
||||
/// semantic index in order to generate the correct accessibility
|
||||
/// announcements. This should only be set to false if the indexes have
|
||||
/// already been provided by an [IndexedSemantics] widget.
|
||||
///
|
||||
/// Defaults to true.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [IndexedSemantics], for an explanation of how to manually
|
||||
/// provide semantic indexes.
|
||||
final bool addSemanticIndexes;
|
||||
|
||||
/// An initial offset to add to the semantic indexes generated by this widget.
|
||||
///
|
||||
/// Defaults to zero.
|
||||
final int semanticIndexOffset;
|
||||
|
||||
/// A [SemanticIndexCallback] which is used when [addSemanticIndexes] is true.
|
||||
///
|
||||
/// Defaults to providing an index for each widget.
|
||||
final SemanticIndexCallback semanticIndexCallback;
|
||||
|
||||
/// The widgets to display.
|
||||
///
|
||||
/// If this list is going to be mutated, it is usually wise to put a [Key] on
|
||||
/// each of the child widgets, so that the framework can match old
|
||||
/// configurations to new configurations and maintain the underlying render
|
||||
/// objects.
|
||||
///
|
||||
/// Also, a [Widget] in Flutter is immutable, so directly modifying the
|
||||
/// [children] such as `someWidget.children.add(...)` or
|
||||
/// passing a reference of the original list value to the [children] parameter
|
||||
/// will result in incorrect behaviors. Whenever the
|
||||
/// children list is modified, a new list object should be provided.
|
||||
///
|
||||
/// The following code corrects the problem mentioned above.
|
||||
///
|
||||
/// ```dart
|
||||
/// class SomeWidgetState extends State<SomeWidget> {
|
||||
/// final List<Widget> _children = <Widget>[];
|
||||
///
|
||||
/// void someHandler() {
|
||||
/// setState(() {
|
||||
/// // The key here allows Flutter to reuse the underlying render
|
||||
/// // objects even if the children list is recreated.
|
||||
/// _children.add(ChildWidget(key: UniqueKey()));
|
||||
/// });
|
||||
/// }
|
||||
///
|
||||
/// @override
|
||||
/// Widget build(BuildContext context) {
|
||||
/// // Always create a new list of children as a Widget is immutable.
|
||||
/// return PageView(children: List<Widget>.of(_children));
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
final List<Widget> children;
|
||||
|
||||
/// A map to cache key to index lookup for children.
|
||||
///
|
||||
/// _keyToIndex[null] is used as current index during the lazy loading process
|
||||
/// in [_findChildIndex]. _keyToIndex should never be used for looking up null key.
|
||||
final Map<Key?, int>? _keyToIndex;
|
||||
|
||||
bool get _isConstantInstance => _keyToIndex == null;
|
||||
|
||||
int? _findChildIndex(Key key) {
|
||||
if (_isConstantInstance) {
|
||||
return null;
|
||||
}
|
||||
// Lazily fill the [_keyToIndex].
|
||||
if (!_keyToIndex!.containsKey(key)) {
|
||||
int index = _keyToIndex![null]!;
|
||||
while (index < children.length) {
|
||||
final Widget child = children[index];
|
||||
if (child.key != null) {
|
||||
_keyToIndex![child.key] = index;
|
||||
}
|
||||
if (child.key == key) {
|
||||
// Record current index for next function call.
|
||||
_keyToIndex![null] = index + 1;
|
||||
return index;
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
_keyToIndex![null] = index;
|
||||
} else {
|
||||
return _keyToIndex![key];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
int? findIndexByKey(Key key) {
|
||||
final Key childKey;
|
||||
if (key is _SaltedValueKey) {
|
||||
final _SaltedValueKey saltedValueKey = key;
|
||||
childKey = saltedValueKey.value;
|
||||
} else {
|
||||
childKey = key;
|
||||
}
|
||||
return _findChildIndex(childKey);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget? build(BuildContext context, int index) {
|
||||
if (index < 0 || index >= children.length) {
|
||||
return null;
|
||||
}
|
||||
Widget child = children[index];
|
||||
final Key? key = child.key != null? _SaltedValueKey(child.key!) : null;
|
||||
if (addRepaintBoundaries) {
|
||||
child = RepaintBoundary(child: child);
|
||||
}
|
||||
if (addSemanticIndexes) {
|
||||
final int? semanticIndex = semanticIndexCallback(child, index);
|
||||
if (semanticIndex != null) {
|
||||
child = IndexedSemantics(index: semanticIndex + semanticIndexOffset, child: child);
|
||||
}
|
||||
}
|
||||
if (addAutomaticKeepAlives) {
|
||||
child = AutomaticKeepAlive(child: _SelectionKeepAlive(child: child));
|
||||
}
|
||||
|
||||
return KeyedSubtree(key: key, child: child);
|
||||
}
|
||||
|
||||
@override
|
||||
int? get estimatedChildCount => children.length;
|
||||
|
||||
@override
|
||||
bool shouldRebuild(covariant SliverChildListDelegate oldDelegate) {
|
||||
return children != oldDelegate.children;
|
||||
}
|
||||
}
|
||||
class _SelectionKeepAlive extends StatefulWidget {
|
||||
/// Creates a widget that listens to [KeepAliveNotification]s and maintains a
|
||||
/// [KeepAlive] widget appropriately.
|
||||
const _SelectionKeepAlive({
|
||||
required this.child,
|
||||
});
|
||||
|
||||
/// The widget below this widget in the tree.
|
||||
///
|
||||
/// {@macro flutter.widgets.ProxyWidget.child}
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
State<_SelectionKeepAlive> createState() => _SelectionKeepAliveState();
|
||||
}
|
||||
|
||||
class _SelectionKeepAliveState extends State<_SelectionKeepAlive> with AutomaticKeepAliveClientMixin implements SelectionRegistrar {
|
||||
Set<Selectable>? _selectablesWithSelections;
|
||||
Map<Selectable, VoidCallback>? _selectableAttachments;
|
||||
SelectionRegistrar? _registrar;
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => _wantKeepAlive;
|
||||
bool _wantKeepAlive = false;
|
||||
set wantKeepAlive(bool value) {
|
||||
if (_wantKeepAlive != value) {
|
||||
_wantKeepAlive = value;
|
||||
updateKeepAlive();
|
||||
}
|
||||
}
|
||||
|
||||
VoidCallback listensTo(Selectable selectable) {
|
||||
return () {
|
||||
if (selectable.value.hasSelection) {
|
||||
_updateSelectablesWithSelections(selectable, add: true);
|
||||
} else {
|
||||
_updateSelectablesWithSelections(selectable, add: false);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void _updateSelectablesWithSelections(Selectable selectable, {required bool add}) {
|
||||
if (add) {
|
||||
assert(selectable.value.hasSelection);
|
||||
_selectablesWithSelections ??= <Selectable>{};
|
||||
_selectablesWithSelections!.add(selectable);
|
||||
} else {
|
||||
_selectablesWithSelections?.remove(selectable);
|
||||
}
|
||||
wantKeepAlive = _selectablesWithSelections?.isNotEmpty ?? false;
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
final SelectionRegistrar? newRegistrar = SelectionContainer.maybeOf(context);
|
||||
if (_registrar != newRegistrar) {
|
||||
if (_registrar != null) {
|
||||
_selectableAttachments?.keys.forEach(_registrar!.remove);
|
||||
}
|
||||
_registrar = newRegistrar;
|
||||
if (_registrar != null) {
|
||||
_selectableAttachments?.keys.forEach(_registrar!.add);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void add(Selectable selectable) {
|
||||
final VoidCallback attachment = listensTo(selectable);
|
||||
selectable.addListener(attachment);
|
||||
_selectableAttachments ??= <Selectable, VoidCallback>{};
|
||||
_selectableAttachments![selectable] = attachment;
|
||||
_registrar!.add(selectable);
|
||||
if (selectable.value.hasSelection) {
|
||||
_updateSelectablesWithSelections(selectable, add: true);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void remove(Selectable selectable) {
|
||||
if (_selectableAttachments == null) {
|
||||
return;
|
||||
}
|
||||
assert(_selectableAttachments!.containsKey(selectable));
|
||||
final VoidCallback attachment = _selectableAttachments!.remove(selectable)!;
|
||||
selectable.removeListener(attachment);
|
||||
_registrar!.remove(selectable);
|
||||
_updateSelectablesWithSelections(selectable, add: false);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (_selectableAttachments != null) {
|
||||
for (final Selectable selectable in _selectableAttachments!.keys) {
|
||||
_registrar!.remove(selectable);
|
||||
selectable.removeListener(_selectableAttachments![selectable]!);
|
||||
}
|
||||
_selectableAttachments = null;
|
||||
}
|
||||
_selectablesWithSelections = null;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
if (_registrar == null) {
|
||||
return widget.child;
|
||||
}
|
||||
return SelectionRegistrarScope(
|
||||
registrar: this,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A base class for sliver that have [KeepAlive] children.
|
||||
/// A base class for slivers that have [KeepAlive] children.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
@ -897,7 +33,7 @@ abstract class SliverWithKeepAliveWidget extends RenderObjectWidget {
|
||||
RenderSliverWithKeepAliveMixin createRenderObject(BuildContext context);
|
||||
}
|
||||
|
||||
/// A base class for sliver that have multiple box children.
|
||||
/// A base class for slivers that have multiple box children.
|
||||
///
|
||||
/// Helps subclasses build their children lazily using a [SliverChildDelegate].
|
||||
///
|
||||
@ -2224,15 +1360,3 @@ class KeepAlive extends ParentDataWidget<KeepAliveParentDataMixin> {
|
||||
properties.add(DiagnosticsProperty<bool>('keepAlive', keepAlive));
|
||||
}
|
||||
}
|
||||
|
||||
// Return a Widget for the given Exception
|
||||
Widget _createErrorWidget(Object exception, StackTrace stackTrace) {
|
||||
final FlutterErrorDetails details = FlutterErrorDetails(
|
||||
exception: exception,
|
||||
stack: stackTrace,
|
||||
library: 'widgets library',
|
||||
context: ErrorDescription('building'),
|
||||
);
|
||||
FlutterError.reportError(details);
|
||||
return ErrorWidget.builder(details);
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
import 'framework.dart';
|
||||
import 'scroll_delegate.dart';
|
||||
import 'sliver.dart';
|
||||
|
||||
/// A sliver that contains multiple box children that each fills the viewport.
|
||||
|
@ -5,6 +5,7 @@
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
import 'framework.dart';
|
||||
import 'scroll_delegate.dart';
|
||||
import 'sliver.dart';
|
||||
|
||||
/// A sliver that places its box children in a linear array and constrains them
|
||||
|
@ -106,6 +106,7 @@ export 'src/widgets/scroll_aware_image_provider.dart';
|
||||
export 'src/widgets/scroll_configuration.dart';
|
||||
export 'src/widgets/scroll_context.dart';
|
||||
export 'src/widgets/scroll_controller.dart';
|
||||
export 'src/widgets/scroll_delegate.dart';
|
||||
export 'src/widgets/scroll_metrics.dart';
|
||||
export 'src/widgets/scroll_notification.dart';
|
||||
export 'src/widgets/scroll_notification_observer.dart';
|
||||
|
Loading…
x
Reference in New Issue
Block a user