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