| 1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | /// @docImport 'basic.dart'; |
| 6 | /// @docImport 'scroll_view.dart'; |
| 7 | /// @docImport 'sliver_fill.dart'; |
| 8 | library; |
| 9 | |
| 10 | import 'package:flutter/rendering.dart'; |
| 11 | |
| 12 | import 'framework.dart'; |
| 13 | import 'scroll_delegate.dart'; |
| 14 | import 'sliver.dart'; |
| 15 | |
| 16 | /// A sliver that places its box children in a linear array and constrains them |
| 17 | /// to have the same extent as a prototype item along the main axis. |
| 18 | /// |
| 19 | /// _To learn more about slivers, see [CustomScrollView.slivers]._ |
| 20 | /// |
| 21 | /// [SliverPrototypeExtentList] arranges its children in a line along |
| 22 | /// the main axis starting at offset zero and without gaps. Each child is |
| 23 | /// constrained to the same extent as the [prototypeItem] along the main axis |
| 24 | /// and the [SliverConstraints.crossAxisExtent] along the cross axis. |
| 25 | /// |
| 26 | /// [SliverPrototypeExtentList] is more efficient than [SliverList] because |
| 27 | /// [SliverPrototypeExtentList] does not need to lay out its children to obtain |
| 28 | /// their extent along the main axis. It's a little more flexible than |
| 29 | /// [SliverFixedExtentList] because there's no need to determine the appropriate |
| 30 | /// item extent in pixels. |
| 31 | /// |
| 32 | /// See also: |
| 33 | /// |
| 34 | /// * [SliverFixedExtentList], whose children are forced to a given pixel |
| 35 | /// extent. |
| 36 | /// * [SliverVariedExtentList], which supports children with varying (but known |
| 37 | /// upfront) extents. |
| 38 | /// * [SliverList], which does not require its children to have the same |
| 39 | /// extent in the main axis. |
| 40 | /// * [SliverFillViewport], which sizes its children based on the |
| 41 | /// size of the viewport, regardless of what else is in the scroll view. |
| 42 | class SliverPrototypeExtentList extends SliverMultiBoxAdaptorWidget { |
| 43 | /// Creates a sliver that places its box children in a linear array and |
| 44 | /// constrains them to have the same extent as a prototype item along |
| 45 | /// the main axis. |
| 46 | const SliverPrototypeExtentList({ |
| 47 | super.key, |
| 48 | required super.delegate, |
| 49 | required this.prototypeItem, |
| 50 | }); |
| 51 | |
| 52 | /// A sliver that places its box children in a linear array and constrains them |
| 53 | /// to have the same extent as a prototype item along the main axis. |
| 54 | /// |
| 55 | /// This constructor is appropriate for sliver lists with a large (or |
| 56 | /// infinite) number of children whose extent is already determined. |
| 57 | /// |
| 58 | /// Providing a non-null `itemCount` improves the ability of the [SliverGrid] |
| 59 | /// to estimate the maximum scroll extent. |
| 60 | /// |
| 61 | /// `itemBuilder` will be called only with indices greater than or equal to |
| 62 | /// zero and less than `itemCount`. |
| 63 | /// |
| 64 | /// {@macro flutter.widgets.ListView.builder.itemBuilder} |
| 65 | /// |
| 66 | /// The `prototypeItem` argument is used to determine the extent of each item. |
| 67 | /// |
| 68 | /// {@macro flutter.widgets.PageView.findChildIndexCallback} |
| 69 | /// |
| 70 | /// The `addAutomaticKeepAlives` argument corresponds to the |
| 71 | /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The |
| 72 | /// `addRepaintBoundaries` argument corresponds to the |
| 73 | /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The |
| 74 | /// `addSemanticIndexes` argument corresponds to the |
| 75 | /// [SliverChildBuilderDelegate.addSemanticIndexes] property. |
| 76 | /// |
| 77 | /// {@tool snippet} |
| 78 | /// This example, which would be inserted into a [CustomScrollView.slivers] |
| 79 | /// list, shows an infinite number of items in varying shades of blue: |
| 80 | /// |
| 81 | /// ```dart |
| 82 | /// SliverPrototypeExtentList.builder( |
| 83 | /// prototypeItem: Container( |
| 84 | /// alignment: Alignment.center, |
| 85 | /// child: const Text('list item prototype'), |
| 86 | /// ), |
| 87 | /// itemBuilder: (BuildContext context, int index) { |
| 88 | /// return Container( |
| 89 | /// alignment: Alignment.center, |
| 90 | /// color: Colors.lightBlue[100 * (index % 9)], |
| 91 | /// child: Text('list item $index'), |
| 92 | /// ); |
| 93 | /// }, |
| 94 | /// ) |
| 95 | /// ``` |
| 96 | /// {@end-tool} |
| 97 | SliverPrototypeExtentList.builder({ |
| 98 | super.key, |
| 99 | required NullableIndexedWidgetBuilder itemBuilder, |
| 100 | required this.prototypeItem, |
| 101 | ChildIndexGetter? findChildIndexCallback, |
| 102 | int? itemCount, |
| 103 | bool addAutomaticKeepAlives = true, |
| 104 | bool addRepaintBoundaries = true, |
| 105 | bool addSemanticIndexes = true, |
| 106 | }) : super( |
| 107 | delegate: SliverChildBuilderDelegate( |
| 108 | itemBuilder, |
| 109 | findChildIndexCallback: findChildIndexCallback, |
| 110 | childCount: itemCount, |
| 111 | addAutomaticKeepAlives: addAutomaticKeepAlives, |
| 112 | addRepaintBoundaries: addRepaintBoundaries, |
| 113 | addSemanticIndexes: addSemanticIndexes, |
| 114 | ), |
| 115 | ); |
| 116 | |
| 117 | /// A sliver that places multiple box children in a linear array along the main |
| 118 | /// axis. |
| 119 | /// |
| 120 | /// This constructor uses a list of [Widget]s to build the sliver. |
| 121 | /// |
| 122 | /// The `addAutomaticKeepAlives` argument corresponds to the |
| 123 | /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The |
| 124 | /// `addRepaintBoundaries` argument corresponds to the |
| 125 | /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The |
| 126 | /// `addSemanticIndexes` argument corresponds to the |
| 127 | /// [SliverChildBuilderDelegate.addSemanticIndexes] property. |
| 128 | /// |
| 129 | /// {@tool snippet} |
| 130 | /// This example, which would be inserted into a [CustomScrollView.slivers] |
| 131 | /// list, shows an infinite number of items in varying shades of blue: |
| 132 | /// |
| 133 | /// ```dart |
| 134 | /// SliverPrototypeExtentList.list( |
| 135 | /// prototypeItem: const Text('Hello'), |
| 136 | /// children: const <Widget>[ |
| 137 | /// Text('Hello'), |
| 138 | /// Text('World!'), |
| 139 | /// ], |
| 140 | /// ); |
| 141 | /// ``` |
| 142 | /// {@end-tool} |
| 143 | SliverPrototypeExtentList.list({ |
| 144 | super.key, |
| 145 | required List<Widget> children, |
| 146 | required this.prototypeItem, |
| 147 | bool addAutomaticKeepAlives = true, |
| 148 | bool addRepaintBoundaries = true, |
| 149 | bool addSemanticIndexes = true, |
| 150 | }) : super( |
| 151 | delegate: SliverChildListDelegate( |
| 152 | children, |
| 153 | addAutomaticKeepAlives: addAutomaticKeepAlives, |
| 154 | addRepaintBoundaries: addRepaintBoundaries, |
| 155 | addSemanticIndexes: addSemanticIndexes, |
| 156 | ), |
| 157 | ); |
| 158 | |
| 159 | /// Defines the main axis extent of all of this sliver's children. |
| 160 | /// |
| 161 | /// The [prototypeItem] is laid out before the rest of the sliver's children |
| 162 | /// and its size along the main axis fixes the size of each child. The |
| 163 | /// [prototypeItem] is essentially [Offstage]: it is not painted and it |
| 164 | /// cannot respond to input. |
| 165 | final Widget prototypeItem; |
| 166 | |
| 167 | @override |
| 168 | RenderSliverMultiBoxAdaptor createRenderObject(BuildContext context) { |
| 169 | final _SliverPrototypeExtentListElement element = context as _SliverPrototypeExtentListElement; |
| 170 | return _RenderSliverPrototypeExtentList(childManager: element); |
| 171 | } |
| 172 | |
| 173 | @override |
| 174 | SliverMultiBoxAdaptorElement createElement() => _SliverPrototypeExtentListElement(this); |
| 175 | } |
| 176 | |
| 177 | class _SliverPrototypeExtentListElement extends SliverMultiBoxAdaptorElement { |
| 178 | _SliverPrototypeExtentListElement(SliverPrototypeExtentList super.widget); |
| 179 | |
| 180 | @override |
| 181 | _RenderSliverPrototypeExtentList get renderObject => |
| 182 | super.renderObject as _RenderSliverPrototypeExtentList; |
| 183 | |
| 184 | Element? _prototype; |
| 185 | static final Object _prototypeSlot = Object(); |
| 186 | |
| 187 | @override |
| 188 | void insertRenderObjectChild(covariant RenderObject child, covariant Object slot) { |
| 189 | if (slot == _prototypeSlot) { |
| 190 | assert(child is RenderBox); |
| 191 | renderObject.child = child as RenderBox; |
| 192 | } else { |
| 193 | super.insertRenderObjectChild(child, slot as int); |
| 194 | } |
| 195 | } |
| 196 | |
| 197 | @override |
| 198 | void didAdoptChild(RenderBox child) { |
| 199 | if (child != renderObject.child) { |
| 200 | super.didAdoptChild(child); |
| 201 | } |
| 202 | } |
| 203 | |
| 204 | @override |
| 205 | void moveRenderObjectChild(RenderBox child, Object oldSlot, Object newSlot) { |
| 206 | if (newSlot == _prototypeSlot) { |
| 207 | // There's only one prototype child so it cannot be moved. |
| 208 | assert(false); |
| 209 | } else { |
| 210 | super.moveRenderObjectChild(child, oldSlot as int, newSlot as int); |
| 211 | } |
| 212 | } |
| 213 | |
| 214 | @override |
| 215 | void removeRenderObjectChild(RenderBox child, Object slot) { |
| 216 | if (renderObject.child == child) { |
| 217 | renderObject.child = null; |
| 218 | } else { |
| 219 | super.removeRenderObjectChild(child, slot as int); |
| 220 | } |
| 221 | } |
| 222 | |
| 223 | @override |
| 224 | void visitChildren(ElementVisitor visitor) { |
| 225 | if (_prototype != null) { |
| 226 | visitor(_prototype!); |
| 227 | } |
| 228 | super.visitChildren(visitor); |
| 229 | } |
| 230 | |
| 231 | @override |
| 232 | void mount(Element? parent, Object? newSlot) { |
| 233 | super.mount(parent, newSlot); |
| 234 | _prototype = updateChild( |
| 235 | _prototype, |
| 236 | (widget as SliverPrototypeExtentList).prototypeItem, |
| 237 | _prototypeSlot, |
| 238 | ); |
| 239 | } |
| 240 | |
| 241 | @override |
| 242 | void update(SliverPrototypeExtentList newWidget) { |
| 243 | super.update(newWidget); |
| 244 | assert(widget == newWidget); |
| 245 | _prototype = updateChild( |
| 246 | _prototype, |
| 247 | (widget as SliverPrototypeExtentList).prototypeItem, |
| 248 | _prototypeSlot, |
| 249 | ); |
| 250 | } |
| 251 | } |
| 252 | |
| 253 | class _RenderSliverPrototypeExtentList extends RenderSliverFixedExtentBoxAdaptor { |
| 254 | _RenderSliverPrototypeExtentList({required _SliverPrototypeExtentListElement childManager}) |
| 255 | : super(childManager: childManager); |
| 256 | |
| 257 | RenderBox? _child; |
| 258 | RenderBox? get child => _child; |
| 259 | set child(RenderBox? value) { |
| 260 | if (_child != null) { |
| 261 | dropChild(_child!); |
| 262 | } |
| 263 | _child = value; |
| 264 | if (_child != null) { |
| 265 | adoptChild(_child!); |
| 266 | } |
| 267 | markNeedsLayout(); |
| 268 | } |
| 269 | |
| 270 | @override |
| 271 | void performLayout() { |
| 272 | child!.layout(constraints.asBoxConstraints(), parentUsesSize: true); |
| 273 | super.performLayout(); |
| 274 | } |
| 275 | |
| 276 | @override |
| 277 | void attach(PipelineOwner owner) { |
| 278 | super.attach(owner); |
| 279 | _child?.attach(owner); |
| 280 | } |
| 281 | |
| 282 | @override |
| 283 | void detach() { |
| 284 | super.detach(); |
| 285 | _child?.detach(); |
| 286 | } |
| 287 | |
| 288 | @override |
| 289 | void redepthChildren() { |
| 290 | if (_child != null) { |
| 291 | redepthChild(_child!); |
| 292 | } |
| 293 | super.redepthChildren(); |
| 294 | } |
| 295 | |
| 296 | @override |
| 297 | void visitChildren(RenderObjectVisitor visitor) { |
| 298 | if (_child != null) { |
| 299 | visitor(_child!); |
| 300 | } |
| 301 | super.visitChildren(visitor); |
| 302 | } |
| 303 | |
| 304 | @override |
| 305 | double get itemExtent { |
| 306 | assert(child != null && child!.hasSize); |
| 307 | return constraints.axis == Axis.vertical ? child!.size.height : child!.size.width; |
| 308 | } |
| 309 | } |
| 310 | |