blob: 38a3cba5ab43e24d0dd5442501a56277c596eb06 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:collection' show SplayTreeMap, HashMap;
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'automatic_keep_alive.dart';
import 'basic.dart';
import 'framework.dart';
export 'package:flutter/rendering.dart' show
SliverGridDelegate,
SliverGridDelegateWithFixedCrossAxisCount,
SliverGridDelegateWithMaxCrossAxisExtent;
/// 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.
///
/// 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.
///
/// Subclasses typically override this function and wrap their children in
/// [AutomaticKeepAlive] and [RepaintBoundary] widgets.
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.
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);
@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) {
description.add('estimated child count: EXCEPTION (${e.runtimeType})');
}
}
}
/// 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 an [IndexedWidgetBuilder] 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).
///
/// See also:
///
/// * [SliverChildListDelegate], which is a delegate that has an explicit list
/// of children.
class SliverChildBuilderDelegate extends SliverChildDelegate {
/// Creates a delegate that supplies children for slivers using the given
/// builder callback.
///
/// The [builder], [addAutomaticKeepAlives], and [addRepaintBoundaries]
/// arguments must not be null.
const SliverChildBuilderDelegate(
this.builder, {
this.childCount,
this.addAutomaticKeepAlives: true,
this.addRepaintBoundaries: true,
}) : assert(builder != null),
assert(addAutomaticKeepAlives != null),
assert(addRepaintBoundaries != null);
/// Called to build children for the sliver.
///
/// Will be called only for indices greater than or equal to zero and less
/// than [childCount] (if [childCount] is non-null).
///
/// Should return null if asked to build a widget with a greater index than
/// exists.
///
/// The delegate wraps the children returned by this builder in
/// [RepaintBoundary] widgets.
final IndexedWidgetBuilder builder;
/// The total number of children this delegate can provide.
///
/// If null, the number of children is determined by the least index for which
/// [builder] returns null.
final int childCount;
/// Whether to wrap each child in an [AutomaticKeepAlive].
///
/// Typically, children in lazy list are wrapped in [AutomaticKeepAlive]
/// widgets so that children can use [KeepAliveNotification]s to preserve
/// their state when they would otherwise be garbage collected off-screen.
///
/// This feature (and [addRepaintBoundaries]) must be disabled if the children
/// are going to manually maintain their [KeepAlive] state. It may also be
/// more efficient to disable this feature if it is known ahead of time that
/// none of the children will ever try to keep themselves alive.
///
/// Defaults to true.
final bool addAutomaticKeepAlives;
/// Whether to wrap each child in a [RepaintBoundary].
///
/// Typically, children in a scrolling container are wrapped in repaint
/// boundaries so that they do not need to be repainted as the list scrolls.
/// If the children are easy to repaint (e.g., solid color blocks or a short
/// snippet of text), it might be more efficient to not add a repaint boundary
/// and simply repaint the children during scrolling.
///
/// Defaults to true.
final bool addRepaintBoundaries;
@override
Widget build(BuildContext context, int index) {
assert(builder != null);
if (index < 0 || (childCount != null && index >= childCount))
return null;
Widget child = builder(context, index);
if (child == null)
return null;
if (addRepaintBoundaries)
child = new RepaintBoundary.wrap(child, index);
if (addAutomaticKeepAlives)
child = new AutomaticKeepAlive(child: child);
return 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).
///
/// 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], and [addRepaintBoundaries]
/// arguments must not be null.
const SliverChildListDelegate(
this.children, {
this.addAutomaticKeepAlives: true,
this.addRepaintBoundaries: true,
}) : assert(children != null),
assert(addAutomaticKeepAlives != null),
assert(addRepaintBoundaries != null);
/// Whether to wrap each child in an [AutomaticKeepAlive].
///
/// Typically, children in lazy list are wrapped in [AutomaticKeepAlive]
/// widgets so that children can use [KeepAliveNotification]s to preserve
/// their state when they would otherwise be garbage collected off-screen.
///
/// This feature (and [addRepaintBoundaries]) must be disabled if the children
/// are going to manually maintain their [KeepAlive] state. It may also be
/// more efficient to disable this feature if it is known ahead of time that
/// none of the children will ever try to keep themselves alive.
///
/// Defaults to true.
final bool addAutomaticKeepAlives;
/// Whether to wrap each child in a [RepaintBoundary].
///
/// Typically, children in a scrolling container are wrapped in repaint
/// boundaries so that they do not need to be repainted as the list scrolls.
/// If the children are easy to repaint (e.g., solid color blocks or a short
/// snippet of text), it might be more efficient to not add a repaint boundary
/// and simply repaint the children during scrolling.
///
/// Defaults to true.
final bool addRepaintBoundaries;
/// The widgets to display.
final List<Widget> children;
@override
Widget build(BuildContext context, int index) {
assert(children != null);
if (index < 0 || index >= children.length)
return null;
Widget child = children[index];
assert(child != null);
if (addRepaintBoundaries)
child = new RepaintBoundary.wrap(child, index);
if (addAutomaticKeepAlives)
child = new AutomaticKeepAlive(child: child);
return child;
}
@override
int get estimatedChildCount => children.length;
@override
bool shouldRebuild(covariant SliverChildListDelegate oldDelegate) {
return children != oldDelegate.children;
}
}
/// A base class for sliver that have multiple box children.
///
/// Helps subclasses build their children lazily using a [SliverChildDelegate].
abstract class SliverMultiBoxAdaptorWidget extends RenderObjectWidget {
/// Initializes fields for subclasses.
const SliverMultiBoxAdaptorWidget({
Key key,
@required this.delegate,
}) : assert(delegate != null),
super(key: key);
/// The delegate that provides the children for this widget.
///
/// The children are constructed lazily using this widget to avoid creating
/// more children than are visible through the [Viewport].
///
/// See also:
///
/// * [SliverChildBuilderDelegate] and [SliverChildListDelegate], which are
/// commonly used subclasses of [SliverChildDelegate] that use a builder
/// callback and an explicit child list, respectively.
final SliverChildDelegate delegate;
@override
SliverMultiBoxAdaptorElement createElement() => new 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(new DiagnosticsProperty<SliverChildDelegate>('delegate', delegate));
}
}
/// A sliver that places multiple box children in a linear array along the main
/// axis.
///
/// Each child is forced to have the [SliverConstraints.crossAxisExtent] in the
/// cross axis but determines its own main axis extent.
///
/// [SliverList] determines its scroll offset by "dead reckoning" because
/// children outside the visible part of the sliver are not materialized, which
/// means [SliverList] cannot learn their main axis extent. Instead, newly
/// materialized children are placed adjacent to existing children.
///
/// 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.
///
/// See also:
///
/// * [SliverFixedExtentList], which is more efficient for children with
/// the same extent in the main axis.
/// * [SliverPrototypeExtentList], which is similar to [SliverFixedExtentList]
/// except that it uses a prototype list item instead of a pixel value to define
/// the main axis extent of each item.
/// * [SliverGrid], which places its children in arbitrary positions.
class SliverList extends SliverMultiBoxAdaptorWidget {
/// Creates a sliver that places box children in a linear array.
const SliverList({
Key key,
@required SliverChildDelegate delegate,
}) : super(key: key, delegate: delegate);
@override
RenderSliverList createRenderObject(BuildContext context) {
final SliverMultiBoxAdaptorElement element = context;
return new RenderSliverList(childManager: element);
}
}
/// A sliver that places multiple box children with the same main axis extent in
/// a linear array.
///
/// [SliverFixedExtentList] places its children in a linear array along the main
/// axis starting at offset zero and without gaps. Each child is forced to have
/// the [itemExtent] in the main axis and the
/// [SliverConstraints.crossAxisExtent] in the cross axis.
///
/// [SliverFixedExtentList] is more efficient than [SliverList] because
/// [SliverFixedExtentList] does not need to perform layout on its children to
/// obtain their extent in the main axis.
///
/// ## Sample code
///
/// This example, which would be inserted into a [CustomScrollView.slivers]
/// list, shows an infinite number of items in varying shades of blue:
///
/// ```dart
/// new SliverFixedExtentList(
/// itemExtent: 50.0,
/// delegate: new SliverChildBuilderDelegate(
/// (BuildContext context, int index) {
/// return new Container(
/// alignment: Alignment.center,
/// color: Colors.lightBlue[100 * (index % 9)],
/// child: new Text('list item $index'),
/// );
/// },
/// ),
/// )
/// ```
///
/// 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({
Key key,
@required SliverChildDelegate delegate,
@required this.itemExtent,
}) : super(key: key, delegate: delegate);
/// The extent the children are forced to have in the main axis.
final double itemExtent;
@override
RenderSliverFixedExtentList createRenderObject(BuildContext context) {
final SliverMultiBoxAdaptorElement element = context;
return new RenderSliverFixedExtentList(childManager: element, itemExtent: itemExtent);
}
@override
void updateRenderObject(BuildContext context, RenderSliverFixedExtentList renderObject) {
renderObject.itemExtent = itemExtent;
}
}
/// A sliver that places multiple box children in a two dimensional arrangement.
///
/// [SliverGrid] places its children in arbitrary positions determined by
/// [gridDelegate]. Each child is forced to have the size specified by the
/// [gridDelegate].
///
/// The main axis direction of a grid is the direction in which it scrolls; the
/// cross axis direction is the orthogonal direction.
///
/// ## Sample code
///
/// This example, which would be inserted into a [CustomScrollView.slivers]
/// list, shows twenty boxes in a pretty teal grid:
///
/// ```dart
/// new SliverGrid(
/// gridDelegate: new SliverGridDelegateWithMaxCrossAxisExtent(
/// maxCrossAxisExtent: 200.0,
/// mainAxisSpacing: 10.0,
/// crossAxisSpacing: 10.0,
/// childAspectRatio: 4.0,
/// ),
/// delegate: new SliverChildBuilderDelegate(
/// (BuildContext context, int index) {
/// return new Container(
/// alignment: Alignment.center,
/// color: Colors.teal[100 * (index % 9)],
/// child: new Text('grid item $index'),
/// );
/// },
/// childCount: 20,
/// ),
/// )
/// ```
///
/// 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({
Key key,
@required SliverChildDelegate delegate,
@required this.gridDelegate,
}) : super(key: key, delegate: delegate);
/// 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:
///
/// * [new GridView.count], the equivalent constructor for [GridView] widgets.
SliverGrid.count({
Key key,
@required int crossAxisCount,
double mainAxisSpacing: 0.0,
double crossAxisSpacing: 0.0,
double childAspectRatio: 1.0,
List<Widget> children: const <Widget>[],
}) : gridDelegate = new SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount,
mainAxisSpacing: mainAxisSpacing,
crossAxisSpacing: crossAxisSpacing,
childAspectRatio: childAspectRatio,
),
super(key: key, delegate: new 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:
///
/// * [new GridView.extent], the equivalent constructor for [GridView] widgets.
SliverGrid.extent({
Key key,
@required double maxCrossAxisExtent,
double mainAxisSpacing: 0.0,
double crossAxisSpacing: 0.0,
double childAspectRatio: 1.0,
List<Widget> children: const <Widget>[],
}) : gridDelegate = new SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: maxCrossAxisExtent,
mainAxisSpacing: mainAxisSpacing,
crossAxisSpacing: crossAxisSpacing,
childAspectRatio: childAspectRatio,
),
super(key: key, delegate: new 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;
return new 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);
}
}
/// A sliver that contains a multiple box children that each fill the viewport.
///
/// [SliverFillViewport] places its children in a linear array along the main
/// axis. Each child is sized to fill the viewport, both in the main and cross
/// axis.
///
/// See also:
///
/// * [SliverFixedExtentList], which has a configurable
/// [SliverFixedExtentList.itemExtent].
/// * [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.
/// * [SliverList], which does not require its children to have the same
/// extent in the main axis.
class SliverFillViewport extends SliverMultiBoxAdaptorWidget {
/// Creates a sliver whose box children that each fill the viewport.
const SliverFillViewport({
Key key,
@required SliverChildDelegate delegate,
this.viewportFraction: 1.0,
}) : assert(viewportFraction != null),
assert(viewportFraction > 0.0),
super(key: key, delegate: delegate);
/// The fraction of the viewport that each child should fill in the main axis.
///
/// If this fraction is less than 1.0, more than one child will be visible at
/// once. If this fraction is greater than 1.0, each child will be larger than
/// the viewport in the main axis.
final double viewportFraction;
@override
RenderSliverFillViewport createRenderObject(BuildContext context) {
final SliverMultiBoxAdaptorElement element = context;
return new RenderSliverFillViewport(childManager: element, viewportFraction: viewportFraction);
}
@override
void updateRenderObject(BuildContext context, RenderSliverFillViewport renderObject) {
renderObject.viewportFraction = viewportFraction;
}
}
/// 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.
SliverMultiBoxAdaptorElement(SliverMultiBoxAdaptorWidget widget) : super(widget);
@override
SliverMultiBoxAdaptorWidget get widget => super.widget;
@override
RenderSliverMultiBoxAdaptor get renderObject => super.renderObject;
@override
void update(covariant SliverMultiBoxAdaptorWidget newWidget) {
final SliverMultiBoxAdaptorWidget oldWidget = widget;
super.update(newWidget);
final SliverChildDelegate newDelegate = newWidget.delegate;
final SliverChildDelegate oldDelegate = oldWidget.delegate;
if (newDelegate != oldDelegate &&
(newDelegate.runtimeType != oldDelegate.runtimeType || newDelegate.shouldRebuild(oldDelegate)))
performRebuild();
}
// We inflate widgets at two different times:
// 1. When we ourselves are told to rebuild (see performRebuild).
// 2. When our render object needs a new child (see createChild).
// In both cases, we cache the results of calling into our delegate to get the widget,
// so that if we do case 2 later, we don't call the builder again.
// Any time we do case 1, though, we reset the cache.
final Map<int, Widget> _childWidgets = new HashMap<int, Widget>();
final SplayTreeMap<int, Element> _childElements = new SplayTreeMap<int, Element>();
RenderBox _currentBeforeChild;
@override
void performRebuild() {
_childWidgets.clear(); // Reset the cache, as described above.
super.performRebuild();
_currentBeforeChild = null;
assert(_currentlyUpdatingChildIndex == null);
try {
int firstIndex = _childElements.firstKey();
int lastIndex = _childElements.lastKey();
if (_childElements.isEmpty) {
firstIndex = 0;
lastIndex = 0;
} else if (_didUnderflow) {
lastIndex += 1;
}
for (int index = firstIndex; index <= lastIndex; ++index) {
_currentlyUpdatingChildIndex = index;
final Element newChild = updateChild(_childElements[index], _build(index), index);
if (newChild != null) {
_childElements[index] = newChild;
_currentBeforeChild = newChild.renderObject;
} else {
_childElements.remove(index);
}
}
} finally {
_currentlyUpdatingChildIndex = null;
}
}
Widget _build(int index) {
return _childWidgets.putIfAbsent(index, () => 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;
Element newChild;
try {
_currentlyUpdatingChildIndex = index;
newChild = updateChild(_childElements[index], _build(index), index);
} finally {
_currentlyUpdatingChildIndex = null;
}
if (newChild != null) {
_childElements[index] = newChild;
} else {
_childElements.remove(index);
}
});
}
@override
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
final SliverMultiBoxAdaptorParentData oldParentData = child?.renderObject?.parentData;
final Element newChild = super.updateChild(child, newWidget, newSlot);
final SliverMultiBoxAdaptorParentData newParentData = newChild?.renderObject?.parentData;
// Preserve the old layoutOffset if the renderObject was swapped out.
if (oldParentData != newParentData && oldParentData != null && newParentData != null) {
newParentData.layoutOffset = oldParentData.layoutOffset;
}
return newChild;
}
@override
void forgetChild(Element child) {
assert(child != null);
assert(child.slot != null);
assert(_childElements.containsKey(child.slot));
_childElements.remove(child.slot);
}
@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));
});
}
double _extrapolateMaxScrollOffset(
int firstIndex,
int lastIndex,
double leadingScrollOffset,
double trailingScrollOffset,
) {
final int childCount = this.childCount;
if (childCount == null)
return double.infinity;
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,
}) {
return widget.estimateMaxScrollOffset(
constraints,
firstIndex,
lastIndex,
leadingScrollOffset,
trailingScrollOffset,
) ?? _extrapolateMaxScrollOffset(
firstIndex,
lastIndex,
leadingScrollOffset,
trailingScrollOffset,
);
}
@override
int get childCount => widget.delegate.estimatedChildCount;
@override
void didStartLayout() {
assert(debugAssertChildListLocked());
}
@override
void didFinishLayout() {
assert(debugAssertChildListLocked());
final int firstIndex = _childElements.firstKey() ?? 0;
final int lastIndex = _childElements.lastKey() ?? 0;
widget.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;
childParentData.index = _currentlyUpdatingChildIndex;
}
bool _didUnderflow = false;
@override
void setDidUnderflow(bool value) {
_didUnderflow = value;
}
@override
void insertChildRenderObject(covariant RenderObject child, int slot) {
assert(slot != null);
assert(_currentlyUpdatingChildIndex == slot);
assert(renderObject.debugValidateChild(child));
renderObject.insert(child, after: _currentBeforeChild);
assert(() {
final SliverMultiBoxAdaptorParentData childParentData = child.parentData;
assert(slot == childParentData.index);
return true;
}());
}
@override
void moveChildRenderObject(covariant RenderObject child, int slot) {
// TODO(ianh): At some point we should be better about noticing when a
// particular LocalKey changes slot, and handle moving the nodes around.
assert(false);
}
@override
void removeChildRenderObject(covariant RenderObject child) {
assert(_currentlyUpdatingChildIndex != null);
renderObject.remove(child);
}
@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.toList().forEach(visitor);
}
@override
void debugVisitOnstageChildren(ElementVisitor visitor) {
_childElements.values.where((Element child) {
final SliverMultiBoxAdaptorParentData parentData = child.renderObject.parentData;
double itemExtent;
switch (renderObject.constraints.axis) {
case Axis.horizontal:
itemExtent = child.renderObject.paintBounds.width;
break;
case Axis.vertical:
itemExtent = child.renderObject.paintBounds.height;
break;
}
return parentData.layoutOffset < renderObject.constraints.scrollOffset + renderObject.constraints.remainingPaintExtent &&
parentData.layoutOffset + itemExtent > renderObject.constraints.scrollOffset;
}).forEach(visitor);
}
}
/// A sliver that contains a single box child that fills the remaining space in
/// the viewport.
///
/// [SliverFillRemaining] sizes its child to fill the viewport in the cross axis
/// and to fill the remaining space in the viewport in the main axis.
///
/// Typically this will be the last sliver in a viewport, since (by definition)
/// there is never any room for anything beyond this sliver.
///
/// See also:
///
/// * [SliverFillViewport], which sizes its children based on the
/// size of the viewport, regardless of what else is in the scroll view.
/// * [SliverList], which shows a list of variable-sized children in a
/// viewport.
class SliverFillRemaining extends SingleChildRenderObjectWidget {
/// Creates a sliver that fills the remaining space in the viewport.
const SliverFillRemaining({
Key key,
Widget child,
}) : super(key: key, child: child);
@override
RenderSliverFillRemaining createRenderObject(BuildContext context) => new RenderSliverFillRemaining();
}
/// 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 [SliverMultiBoxAdaptorWidget]s, such as
/// [SliverGrid] or [SliverList].
class KeepAlive extends ParentDataWidget<SliverMultiBoxAdaptorWidget> {
/// Marks a child as needing to remain alive.
///
/// The [child] and [keepAlive] arguments must not be null.
const KeepAlive({
Key key,
@required this.keepAlive,
@required Widget child,
}) : assert(child != null),
assert(keepAlive != null),
super(key: key, child: 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 SliverMultiBoxAdaptorParentData);
final SliverMultiBoxAdaptorParentData parentData = renderObject.parentData;
if (parentData.keepAlive != keepAlive) {
parentData.keepAlive = keepAlive;
final AbstractNode targetParent = renderObject.parent;
if (targetParent is RenderObject && !keepAlive)
targetParent.markNeedsLayout(); // No need to redo layout if it became true.
}
}
// We only return true if [keepAlive] is true, because turning _off_ keep
// alive requires a layout to do the garbage collection (but turning it on
// requires nothing, since by definition the widget is already alive and won't
// go away _unless_ we do a layout).
@override
bool debugCanApplyOutOfTurn() => keepAlive;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(new DiagnosticsProperty<bool>('keepAlive', keepAlive));
}
}