blob: 923497faef12eb68d70bcf004a194b69ac912a03 [file] [log] [blame]
// 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/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
/// to have the same extent as a prototype item along the main axis.
///
/// _To learn more about slivers, see [CustomScrollView.slivers]._
///
/// [SliverPrototypeExtentList] arranges its children in a line along
/// the main axis starting at offset zero and without gaps. Each child is
/// constrained to the same extent as the [prototypeItem] along the main axis
/// and the [SliverConstraints.crossAxisExtent] along the cross axis.
///
/// [SliverPrototypeExtentList] is more efficient than [SliverList] because
/// [SliverPrototypeExtentList] does not need to lay out its children to obtain
/// their extent along the main axis. It's a little more flexible than
/// [SliverFixedExtentList] because there's no need to determine the appropriate
/// item extent in pixels.
///
/// See also:
///
/// * [SliverFixedExtentList], whose children are forced to a given pixel
/// extent.
/// * [SliverList], which does not require its children to have the same
/// extent in the main axis.
/// * [SliverFillViewport], which sizes its children based on the
/// size of the viewport, regardless of what else is in the scroll view.
class SliverPrototypeExtentList extends SliverMultiBoxAdaptorWidget {
/// Creates a sliver that places its box children in a linear array and
/// constrains them to have the same extent as a prototype item along
/// the main axis.
const SliverPrototypeExtentList({
super.key,
required super.delegate,
required this.prototypeItem,
});
/// A sliver that places its box children in a linear array and constrains them
/// to have the same extent as a prototype item along the main axis.
///
/// This constructor is appropriate for sliver lists with a large (or
/// infinite) number of children whose extent is already determined.
///
/// Providing a non-null `itemCount` improves the ability of the [SliverGrid]
/// to estimate the maximum scroll extent.
///
/// `itemBuilder` will be called only with indices greater than or equal to
/// zero and less than `itemCount`.
///
/// {@macro flutter.widgets.ListView.builder.itemBuilder}
///
/// The `prototypeItem` argument is used to determine the extent of each item.
///
/// {@macro flutter.widgets.PageView.findChildIndexCallback}
///
/// The `addAutomaticKeepAlives` argument corresponds to the
/// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The
/// `addRepaintBoundaries` argument corresponds to the
/// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The
/// `addSemanticIndexes` argument corresponds to the
/// [SliverChildBuilderDelegate.addSemanticIndexes] property.
///
/// {@tool snippet}
/// This example, which would be inserted into a [CustomScrollView.slivers]
/// list, shows an infinite number of items in varying shades of blue:
///
/// ```dart
/// SliverPrototypeExtentList.builder(
/// prototypeItem: Container(
/// alignment: Alignment.center,
/// child: const Text('list item prototype'),
/// ),
/// itemBuilder: (BuildContext context, int index) {
/// return Container(
/// alignment: Alignment.center,
/// color: Colors.lightBlue[100 * (index % 9)],
/// child: Text('list item $index'),
/// );
/// },
/// )
/// ```
/// {@end-tool}
SliverPrototypeExtentList.builder({
super.key,
required NullableIndexedWidgetBuilder itemBuilder,
required this.prototypeItem,
ChildIndexGetter? findChildIndexCallback,
int? itemCount,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
}) : super(delegate: SliverChildBuilderDelegate(
itemBuilder,
findChildIndexCallback: findChildIndexCallback,
childCount: itemCount,
addAutomaticKeepAlives: addAutomaticKeepAlives,
addRepaintBoundaries: addRepaintBoundaries,
addSemanticIndexes: addSemanticIndexes,
));
/// A sliver that places multiple box children in a linear array along the main
/// axis.
///
/// This constructor uses a list of [Widget]s to build the sliver.
///
/// The `addAutomaticKeepAlives` argument corresponds to the
/// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The
/// `addRepaintBoundaries` argument corresponds to the
/// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The
/// `addSemanticIndexes` argument corresponds to the
/// [SliverChildBuilderDelegate.addSemanticIndexes] property.
///
/// {@tool snippet}
/// This example, which would be inserted into a [CustomScrollView.slivers]
/// list, shows an infinite number of items in varying shades of blue:
///
/// ```dart
/// SliverPrototypeExtentList.list(
/// prototypeItem: const Text('Hello'),
/// children: const <Widget>[
/// Text('Hello'),
/// Text('World!'),
/// ],
/// );
/// ```
/// {@end-tool}
SliverPrototypeExtentList.list({
super.key,
required List<Widget> children,
required this.prototypeItem,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
}) : super(delegate: SliverChildListDelegate(
children,
addAutomaticKeepAlives: addAutomaticKeepAlives,
addRepaintBoundaries: addRepaintBoundaries,
addSemanticIndexes: addSemanticIndexes,
));
/// Defines the main axis extent of all of this sliver's children.
///
/// The [prototypeItem] is laid out before the rest of the sliver's children
/// and its size along the main axis fixes the size of each child. The
/// [prototypeItem] is essentially [Offstage]: it is not painted and it
/// cannot respond to input.
final Widget prototypeItem;
@override
RenderSliverMultiBoxAdaptor createRenderObject(BuildContext context) {
final _SliverPrototypeExtentListElement element = context as _SliverPrototypeExtentListElement;
return _RenderSliverPrototypeExtentList(childManager: element);
}
@override
SliverMultiBoxAdaptorElement createElement() => _SliverPrototypeExtentListElement(this);
}
class _SliverPrototypeExtentListElement extends SliverMultiBoxAdaptorElement {
_SliverPrototypeExtentListElement(SliverPrototypeExtentList super.widget);
@override
_RenderSliverPrototypeExtentList get renderObject => super.renderObject as _RenderSliverPrototypeExtentList;
Element? _prototype;
static final Object _prototypeSlot = Object();
@override
void insertRenderObjectChild(covariant RenderObject child, covariant Object slot) {
if (slot == _prototypeSlot) {
assert(child is RenderBox);
renderObject.child = child as RenderBox;
} else {
super.insertRenderObjectChild(child, slot as int);
}
}
@override
void didAdoptChild(RenderBox child) {
if (child != renderObject.child) {
super.didAdoptChild(child);
}
}
@override
void moveRenderObjectChild(RenderBox child, Object oldSlot, Object newSlot) {
if (newSlot == _prototypeSlot) {
// There's only one prototype child so it cannot be moved.
assert(false);
} else {
super.moveRenderObjectChild(child, oldSlot as int, newSlot as int);
}
}
@override
void removeRenderObjectChild(RenderBox child, Object slot) {
if (renderObject.child == child) {
renderObject.child = null;
} else {
super.removeRenderObjectChild(child, slot as int);
}
}
@override
void visitChildren(ElementVisitor visitor) {
if (_prototype != null) {
visitor(_prototype!);
}
super.visitChildren(visitor);
}
@override
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
_prototype = updateChild(_prototype, (widget as SliverPrototypeExtentList).prototypeItem, _prototypeSlot);
}
@override
void update(SliverPrototypeExtentList newWidget) {
super.update(newWidget);
assert(widget == newWidget);
_prototype = updateChild(_prototype, (widget as SliverPrototypeExtentList).prototypeItem, _prototypeSlot);
}
}
class _RenderSliverPrototypeExtentList extends RenderSliverFixedExtentBoxAdaptor {
_RenderSliverPrototypeExtentList({
required _SliverPrototypeExtentListElement childManager,
}) : super(childManager: childManager);
RenderBox? _child;
RenderBox? get child => _child;
set child(RenderBox? value) {
if (_child != null) {
dropChild(_child!);
}
_child = value;
if (_child != null) {
adoptChild(_child!);
}
markNeedsLayout();
}
@override
void performLayout() {
child!.layout(constraints.asBoxConstraints(), parentUsesSize: true);
super.performLayout();
}
@override
void attach(PipelineOwner owner) {
super.attach(owner);
if (_child != null) {
_child!.attach(owner);
}
}
@override
void detach() {
super.detach();
if (_child != null) {
_child!.detach();
}
}
@override
void redepthChildren() {
if (_child != null) {
redepthChild(_child!);
}
super.redepthChildren();
}
@override
void visitChildren(RenderObjectVisitor visitor) {
if (_child != null) {
visitor(_child!);
}
super.visitChildren(visitor);
}
@override
double get itemExtent {
assert(child != null && child!.hasSize);
return constraints.axis == Axis.vertical ? child!.size.height : child!.size.width;
}
}