| // 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; |
| } |
| } |