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 |
Definitions
- SliverPrototypeExtentList
- SliverPrototypeExtentList
- builder
- list
- createRenderObject
- createElement
- _SliverPrototypeExtentListElement
- _SliverPrototypeExtentListElement
- renderObject
- insertRenderObjectChild
- didAdoptChild
- moveRenderObjectChild
- removeRenderObjectChild
- visitChildren
- mount
- update
- _RenderSliverPrototypeExtentList
- _RenderSliverPrototypeExtentList
- child
- child
- performLayout
- attach
- detach
- redepthChildren
- visitChildren
Learn more about Flutter for embedded and desktop on industrialflutter.com