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 'package:flutter/material.dart'; |
6 | /// |
7 | /// @docImport 'proxy_box.dart'; |
8 | /// @docImport 'proxy_sliver.dart'; |
9 | /// @docImport 'sliver_fill.dart'; |
10 | /// @docImport 'sliver_grid.dart'; |
11 | /// @docImport 'sliver_list.dart'; |
12 | /// @docImport 'sliver_padding.dart'; |
13 | /// @docImport 'sliver_persistent_header.dart'; |
14 | library; |
15 | |
16 | import 'dart:math' as math; |
17 | |
18 | import 'package:flutter/foundation.dart'; |
19 | import 'package:flutter/gestures.dart'; |
20 | |
21 | import 'box.dart'; |
22 | import 'debug.dart'; |
23 | import 'object.dart'; |
24 | import 'viewport.dart'; |
25 | import 'viewport_offset.dart'; |
26 | |
27 | // CORE TYPES FOR SLIVERS |
28 | // The RenderSliver base class and its helper types. |
29 | |
30 | /// Called to get the item extent by the index of item. |
31 | /// |
32 | /// Should return null if asked to build an item extent with a greater index than |
33 | /// exists. |
34 | /// |
35 | /// Used by [ListView.itemExtentBuilder] and [SliverVariedExtentList.itemExtentBuilder]. |
36 | typedef ItemExtentBuilder = double? Function(int index, SliverLayoutDimensions dimensions); |
37 | |
38 | /// Relates the dimensions of the [RenderSliver] during layout. |
39 | /// |
40 | /// Used by [ListView.itemExtentBuilder] and [SliverVariedExtentList.itemExtentBuilder]. |
41 | @immutable |
42 | class SliverLayoutDimensions { |
43 | /// Constructs a [SliverLayoutDimensions] with the specified parameters. |
44 | const SliverLayoutDimensions({ |
45 | required this.scrollOffset, |
46 | required this.precedingScrollExtent, |
47 | required this.viewportMainAxisExtent, |
48 | required this.crossAxisExtent, |
49 | }); |
50 | |
51 | /// {@macro flutter.rendering.SliverConstraints.scrollOffset} |
52 | final double scrollOffset; |
53 | |
54 | /// {@macro flutter.rendering.SliverConstraints.precedingScrollExtent} |
55 | final double precedingScrollExtent; |
56 | |
57 | /// The number of pixels the viewport can display in the main axis. |
58 | /// |
59 | /// For a vertical list, this is the height of the viewport. |
60 | final double viewportMainAxisExtent; |
61 | |
62 | /// The number of pixels in the cross-axis. |
63 | /// |
64 | /// For a vertical list, this is the width of the sliver. |
65 | final double crossAxisExtent; |
66 | |
67 | @override |
68 | bool operator ==(Object other) { |
69 | if (identical(this, other)) { |
70 | return true; |
71 | } |
72 | if (other is! SliverLayoutDimensions) { |
73 | return false; |
74 | } |
75 | return other.scrollOffset == scrollOffset && |
76 | other.precedingScrollExtent == precedingScrollExtent && |
77 | other.viewportMainAxisExtent == viewportMainAxisExtent && |
78 | other.crossAxisExtent == crossAxisExtent; |
79 | } |
80 | |
81 | @override |
82 | String toString() { |
83 | return 'scrollOffset:$scrollOffset ' |
84 | ' precedingScrollExtent:$precedingScrollExtent ' |
85 | ' viewportMainAxisExtent:$viewportMainAxisExtent ' |
86 | ' crossAxisExtent:$crossAxisExtent '; |
87 | } |
88 | |
89 | @override |
90 | int get hashCode => |
91 | Object.hash(scrollOffset, precedingScrollExtent, viewportMainAxisExtent, crossAxisExtent); |
92 | } |
93 | |
94 | /// The direction in which a sliver's contents are ordered, relative to the |
95 | /// scroll offset axis. |
96 | /// |
97 | /// For example, a vertical alphabetical list that is going [AxisDirection.down] |
98 | /// with a [GrowthDirection.forward] would have the A at the top and the Z at |
99 | /// the bottom, with the A adjacent to the origin, as would such a list going |
100 | /// [AxisDirection.up] with a [GrowthDirection.reverse]. On the other hand, a |
101 | /// vertical alphabetical list that is going [AxisDirection.down] with a |
102 | /// [GrowthDirection.reverse] would have the Z at the top (at scroll offset |
103 | /// zero) and the A below it. |
104 | /// |
105 | /// {@template flutter.rendering.GrowthDirection.sample} |
106 | /// Most scroll views by default are ordered [GrowthDirection.forward]. |
107 | /// Changing the default values of [ScrollView.anchor], |
108 | /// [ScrollView.center], or both, can configure a scroll view for |
109 | /// [GrowthDirection.reverse]. |
110 | /// |
111 | /// {@tool dartpad} |
112 | /// This sample shows a [CustomScrollView], with [Radio] buttons in the |
113 | /// [AppBar.bottom] that change the [AxisDirection] to illustrate different |
114 | /// configurations. The [CustomScrollView.anchor] and [CustomScrollView.center] |
115 | /// properties are also set to have the 0 scroll offset positioned in the middle |
116 | /// of the viewport, with [GrowthDirection.forward] and [GrowthDirection.reverse] |
117 | /// illustrated on either side. The sliver that shares the |
118 | /// [CustomScrollView.center] key is positioned at the [CustomScrollView.anchor]. |
119 | /// |
120 | /// ** See code in examples/api/lib/rendering/growth_direction/growth_direction.0.dart ** |
121 | /// {@end-tool} |
122 | /// {@endtemplate} |
123 | /// |
124 | /// See also: |
125 | /// |
126 | /// * [applyGrowthDirectionToAxisDirection], which returns the direction in |
127 | /// which the scroll offset increases. |
128 | enum GrowthDirection { |
129 | /// This sliver's contents are ordered in the same direction as the |
130 | /// [AxisDirection]. For example, a vertical alphabetical list that is going |
131 | /// [AxisDirection.down] with a [GrowthDirection.forward] would have the A at |
132 | /// the top and the Z at the bottom, with the A adjacent to the origin. |
133 | /// |
134 | /// See also: |
135 | /// |
136 | /// * [applyGrowthDirectionToAxisDirection], which returns the direction in |
137 | /// which the scroll offset increases. |
138 | forward, |
139 | |
140 | /// This sliver's contents are ordered in the opposite direction of the |
141 | /// [AxisDirection]. |
142 | /// |
143 | /// See also: |
144 | /// |
145 | /// * [applyGrowthDirectionToAxisDirection], which returns the direction in |
146 | /// which the scroll offset increases. |
147 | reverse, |
148 | } |
149 | |
150 | /// Flips the [AxisDirection] if the [GrowthDirection] is [GrowthDirection.reverse]. |
151 | /// |
152 | /// Specifically, returns `axisDirection` if `growthDirection` is |
153 | /// [GrowthDirection.forward], otherwise returns [flipAxisDirection] applied to |
154 | /// `axisDirection`. |
155 | /// |
156 | /// This function is useful in [RenderSliver] subclasses that are given both an |
157 | /// [AxisDirection] and a [GrowthDirection] and wish to compute the |
158 | /// [AxisDirection] in which growth will occur. |
159 | AxisDirection applyGrowthDirectionToAxisDirection( |
160 | AxisDirection axisDirection, |
161 | GrowthDirection growthDirection, |
162 | ) { |
163 | return switch (growthDirection) { |
164 | GrowthDirection.forward => axisDirection, |
165 | GrowthDirection.reverse => flipAxisDirection(axisDirection), |
166 | }; |
167 | } |
168 | |
169 | /// Flips the [ScrollDirection] if the [GrowthDirection] is |
170 | /// [GrowthDirection.reverse]. |
171 | /// |
172 | /// Specifically, returns `scrollDirection` if `scrollDirection` is |
173 | /// [GrowthDirection.forward], otherwise returns [flipScrollDirection] applied |
174 | /// to `scrollDirection`. |
175 | /// |
176 | /// This function is useful in [RenderSliver] subclasses that are given both an |
177 | /// [ScrollDirection] and a [GrowthDirection] and wish to compute the |
178 | /// [ScrollDirection] in which growth will occur. |
179 | ScrollDirection applyGrowthDirectionToScrollDirection( |
180 | ScrollDirection scrollDirection, |
181 | GrowthDirection growthDirection, |
182 | ) { |
183 | return switch (growthDirection) { |
184 | GrowthDirection.forward => scrollDirection, |
185 | GrowthDirection.reverse => flipScrollDirection(scrollDirection), |
186 | }; |
187 | } |
188 | |
189 | /// Immutable layout constraints for [RenderSliver] layout. |
190 | /// |
191 | /// The [SliverConstraints] describe the current scroll state of the viewport |
192 | /// from the point of view of the sliver receiving the constraints. For example, |
193 | /// a [scrollOffset] of zero means that the leading edge of the sliver is |
194 | /// visible in the viewport, not that the viewport itself has a zero scroll |
195 | /// offset. |
196 | class SliverConstraints extends Constraints { |
197 | /// Creates sliver constraints with the given information. |
198 | const SliverConstraints({ |
199 | required this.axisDirection, |
200 | required this.growthDirection, |
201 | required this.userScrollDirection, |
202 | required this.scrollOffset, |
203 | required this.precedingScrollExtent, |
204 | required this.overlap, |
205 | required this.remainingPaintExtent, |
206 | required this.crossAxisExtent, |
207 | required this.crossAxisDirection, |
208 | required this.viewportMainAxisExtent, |
209 | required this.remainingCacheExtent, |
210 | required this.cacheOrigin, |
211 | }); |
212 | |
213 | /// Creates a copy of this object but with the given fields replaced with the |
214 | /// new values. |
215 | SliverConstraints copyWith({ |
216 | AxisDirection? axisDirection, |
217 | GrowthDirection? growthDirection, |
218 | ScrollDirection? userScrollDirection, |
219 | double? scrollOffset, |
220 | double? precedingScrollExtent, |
221 | double? overlap, |
222 | double? remainingPaintExtent, |
223 | double? crossAxisExtent, |
224 | AxisDirection? crossAxisDirection, |
225 | double? viewportMainAxisExtent, |
226 | double? remainingCacheExtent, |
227 | double? cacheOrigin, |
228 | }) { |
229 | return SliverConstraints( |
230 | axisDirection: axisDirection ?? this.axisDirection, |
231 | growthDirection: growthDirection ?? this.growthDirection, |
232 | userScrollDirection: userScrollDirection ?? this.userScrollDirection, |
233 | scrollOffset: scrollOffset ?? this.scrollOffset, |
234 | precedingScrollExtent: precedingScrollExtent ?? this.precedingScrollExtent, |
235 | overlap: overlap ?? this.overlap, |
236 | remainingPaintExtent: remainingPaintExtent ?? this.remainingPaintExtent, |
237 | crossAxisExtent: crossAxisExtent ?? this.crossAxisExtent, |
238 | crossAxisDirection: crossAxisDirection ?? this.crossAxisDirection, |
239 | viewportMainAxisExtent: viewportMainAxisExtent ?? this.viewportMainAxisExtent, |
240 | remainingCacheExtent: remainingCacheExtent ?? this.remainingCacheExtent, |
241 | cacheOrigin: cacheOrigin ?? this.cacheOrigin, |
242 | ); |
243 | } |
244 | |
245 | /// The direction in which the [scrollOffset] and [remainingPaintExtent] |
246 | /// increase. |
247 | /// |
248 | /// {@tool dartpad} |
249 | /// This sample shows a [CustomScrollView], with [Radio] buttons in the |
250 | /// [AppBar.bottom] that change the [AxisDirection] to illustrate different |
251 | /// configurations. |
252 | /// |
253 | /// ** See code in examples/api/lib/painting/axis_direction/axis_direction.0.dart ** |
254 | /// {@end-tool} |
255 | final AxisDirection axisDirection; |
256 | |
257 | /// The direction in which the contents of slivers are ordered, relative to |
258 | /// the [axisDirection]. |
259 | /// |
260 | /// For example, if the [axisDirection] is [AxisDirection.up], and the |
261 | /// [growthDirection] is [GrowthDirection.forward], then an alphabetical list |
262 | /// will have A at the bottom, then B, then C, and so forth, with Z at the |
263 | /// top, with the bottom of the A at scroll offset zero, and the top of the Z |
264 | /// at the highest scroll offset. |
265 | /// |
266 | /// If a viewport has an overall [AxisDirection] of [AxisDirection.down], then |
267 | /// slivers above the absolute zero offset will have an axis of |
268 | /// [AxisDirection.up] and a growth direction of [GrowthDirection.reverse], |
269 | /// while slivers below the absolute zero offset will have the same axis |
270 | /// direction as the viewport and a growth direction of |
271 | /// [GrowthDirection.forward]. (The slivers with a reverse growth direction |
272 | /// still see only positive scroll offsets; the scroll offsets are reversed as |
273 | /// well, with zero at the absolute zero point, and positive numbers going |
274 | /// away from there.) |
275 | /// |
276 | /// Normally, the absolute zero offset is determined by the viewport's |
277 | /// [RenderViewport.center] and [RenderViewport.anchor] properties. |
278 | /// |
279 | /// {@macro flutter.rendering.GrowthDirection.sample} |
280 | final GrowthDirection growthDirection; |
281 | |
282 | /// The direction in which the user is attempting to scroll, relative to the |
283 | /// [axisDirection] and [growthDirection]. |
284 | /// |
285 | /// For example, if [growthDirection] is [GrowthDirection.forward] and |
286 | /// [axisDirection] is [AxisDirection.down], then a |
287 | /// [ScrollDirection.reverse] means that the user is scrolling down, in the |
288 | /// positive [scrollOffset] direction. |
289 | /// |
290 | /// If the _user_ is not scrolling, this will return [ScrollDirection.idle] |
291 | /// even if there is (for example) a [ScrollActivity] currently animating the |
292 | /// position. |
293 | /// |
294 | /// This is used by some slivers to determine how to react to a change in |
295 | /// scroll offset. For example, [RenderSliverFloatingPersistentHeader] will |
296 | /// only expand a floating app bar when the [userScrollDirection] is in the |
297 | /// positive scroll offset direction. |
298 | /// |
299 | /// {@macro flutter.rendering.ScrollDirection.sample} |
300 | final ScrollDirection userScrollDirection; |
301 | |
302 | /// {@template flutter.rendering.SliverConstraints.scrollOffset} |
303 | /// The scroll offset, in this sliver's coordinate system, that corresponds to |
304 | /// the earliest visible part of this sliver in the [AxisDirection] if |
305 | /// [SliverConstraints.growthDirection] is [GrowthDirection.forward] or in the opposite |
306 | /// [AxisDirection] direction if [SliverConstraints.growthDirection] is [GrowthDirection.reverse]. |
307 | /// |
308 | /// For example, if [AxisDirection] is [AxisDirection.down] and [SliverConstraints.growthDirection] |
309 | /// is [GrowthDirection.forward], then scroll offset is the amount the top of |
310 | /// the sliver has been scrolled past the top of the viewport. |
311 | /// |
312 | /// This value is typically used to compute whether this sliver should still |
313 | /// protrude into the viewport via [SliverGeometry.paintExtent] and |
314 | /// [SliverGeometry.layoutExtent] considering how far the beginning of the |
315 | /// sliver is above the beginning of the viewport. |
316 | /// |
317 | /// For slivers whose top is not past the top of the viewport, the |
318 | /// [scrollOffset] is `0` when [AxisDirection] is [AxisDirection.down] and |
319 | /// [SliverConstraints.growthDirection] is [GrowthDirection.forward]. The set of slivers with |
320 | /// [scrollOffset] `0` includes all the slivers that are below the bottom of the |
321 | /// viewport. |
322 | /// |
323 | /// [SliverConstraints.remainingPaintExtent] is typically used to accomplish |
324 | /// the same goal of computing whether scrolled out slivers should still |
325 | /// partially 'protrude in' from the bottom of the viewport. |
326 | /// |
327 | /// Whether this corresponds to the beginning or the end of the sliver's |
328 | /// contents depends on the [SliverConstraints.growthDirection]. |
329 | /// {@endtemplate} |
330 | final double scrollOffset; |
331 | |
332 | /// {@template flutter.rendering.SliverConstraints.precedingScrollExtent} |
333 | /// The scroll distance that has been consumed by all [RenderSliver]s that |
334 | /// came before this [RenderSliver]. |
335 | /// |
336 | /// # Edge Cases |
337 | /// |
338 | /// [RenderSliver]s often lazily create their internal content as layout |
339 | /// occurs, e.g., [SliverList]. In this case, when [RenderSliver]s exceed the |
340 | /// viewport, their children are built lazily, and the [RenderSliver] does not |
341 | /// have enough information to estimate its total extent, |
342 | /// [precedingScrollExtent] will be [double.infinity] for all [RenderSliver]s |
343 | /// that appear after the lazily constructed child. This is because a total |
344 | /// [SliverGeometry.scrollExtent] cannot be calculated unless all inner |
345 | /// children have been created and sized, or the number of children and |
346 | /// estimated extents are provided. The infinite [SliverGeometry.scrollExtent] |
347 | /// will become finite as soon as enough information is available to estimate |
348 | /// the overall extent of all children within the given [RenderSliver]. |
349 | /// |
350 | /// [RenderSliver]s may legitimately be infinite, meaning that they can scroll |
351 | /// content forever without reaching the end. For any [RenderSliver]s that |
352 | /// appear after the infinite [RenderSliver], the [precedingScrollExtent] will |
353 | /// be [double.infinity]. |
354 | /// {@endtemplate} |
355 | final double precedingScrollExtent; |
356 | |
357 | /// The number of pixels from where the pixels corresponding to the |
358 | /// [scrollOffset] will be painted up to the first pixel that has not yet been |
359 | /// painted on by an earlier sliver, in the [axisDirection]. |
360 | /// |
361 | /// For example, if the previous sliver had a [SliverGeometry.paintExtent] of |
362 | /// 100.0 pixels but a [SliverGeometry.layoutExtent] of only 50.0 pixels, |
363 | /// then the [overlap] of this sliver will be 50.0. |
364 | /// |
365 | /// This is typically ignored unless the sliver is itself going to be pinned |
366 | /// or floating and wants to avoid doing so under the previous sliver. |
367 | final double overlap; |
368 | |
369 | /// The number of pixels of content that the sliver should consider providing. |
370 | /// (Providing more pixels than this is inefficient.) |
371 | /// |
372 | /// The actual number of pixels provided should be specified in the |
373 | /// [RenderSliver.geometry] as [SliverGeometry.paintExtent]. |
374 | /// |
375 | /// This value may be infinite, for example if the viewport is an |
376 | /// unconstrained [RenderShrinkWrappingViewport]. |
377 | /// |
378 | /// This value may be 0.0, for example if the sliver is scrolled off the |
379 | /// bottom of a downwards vertical viewport. |
380 | final double remainingPaintExtent; |
381 | |
382 | /// The number of pixels in the cross-axis. |
383 | /// |
384 | /// For a vertical list, this is the width of the sliver. |
385 | final double crossAxisExtent; |
386 | |
387 | /// The direction in which children should be placed in the cross axis. |
388 | /// |
389 | /// Typically used in vertical lists to describe whether the ambient |
390 | /// [TextDirection] is [TextDirection.rtl] or [TextDirection.ltr]. |
391 | final AxisDirection crossAxisDirection; |
392 | |
393 | /// The number of pixels the viewport can display in the main axis. |
394 | /// |
395 | /// For a vertical list, this is the height of the viewport. |
396 | final double viewportMainAxisExtent; |
397 | |
398 | /// Where the cache area starts relative to the [scrollOffset]. |
399 | /// |
400 | /// Slivers that fall into the cache area located before the leading edge and |
401 | /// after the trailing edge of the viewport should still render content |
402 | /// because they are about to become visible when the user scrolls. |
403 | /// |
404 | /// The [cacheOrigin] describes where the [remainingCacheExtent] starts relative |
405 | /// to the [scrollOffset]. A cache origin of 0 means that the sliver does not |
406 | /// have to provide any content before the current [scrollOffset]. A |
407 | /// [cacheOrigin] of -250.0 means that even though the first visible part of |
408 | /// the sliver will be at the provided [scrollOffset], the sliver should |
409 | /// render content starting 250.0 before the [scrollOffset] to fill the |
410 | /// cache area of the viewport. |
411 | /// |
412 | /// The [cacheOrigin] is always negative or zero and will never exceed |
413 | /// -[scrollOffset]. In other words, a sliver is never asked to provide |
414 | /// content before its zero [scrollOffset]. |
415 | /// |
416 | /// See also: |
417 | /// |
418 | /// * [RenderViewport.cacheExtent] for a description of a viewport's cache area. |
419 | final double cacheOrigin; |
420 | |
421 | /// Describes how much content the sliver should provide starting from the |
422 | /// [cacheOrigin]. |
423 | /// |
424 | /// Not all content in the [remainingCacheExtent] will be visible as some |
425 | /// of it might fall into the cache area of the viewport. |
426 | /// |
427 | /// Each sliver should start laying out content at the [cacheOrigin] and |
428 | /// try to provide as much content as the [remainingCacheExtent] allows. |
429 | /// |
430 | /// The [remainingCacheExtent] is always larger or equal to the |
431 | /// [remainingPaintExtent]. Content, that falls in the [remainingCacheExtent], |
432 | /// but is outside of the [remainingPaintExtent] is currently not visible |
433 | /// in the viewport. |
434 | /// |
435 | /// See also: |
436 | /// |
437 | /// * [RenderViewport.cacheExtent] for a description of a viewport's cache area. |
438 | final double remainingCacheExtent; |
439 | |
440 | /// The axis along which the [scrollOffset] and [remainingPaintExtent] are measured. |
441 | Axis get axis => axisDirectionToAxis(axisDirection); |
442 | |
443 | /// Return what the [growthDirection] would be if the [axisDirection] was |
444 | /// either [AxisDirection.down] or [AxisDirection.right]. |
445 | /// |
446 | /// This is the same as [growthDirection] unless the [axisDirection] is either |
447 | /// [AxisDirection.up] or [AxisDirection.left], in which case it is the |
448 | /// opposite growth direction. |
449 | /// |
450 | /// This can be useful in combination with [axis] to view the [axisDirection] |
451 | /// and [growthDirection] in different terms. |
452 | GrowthDirection get normalizedGrowthDirection { |
453 | if (axisDirectionIsReversed(axisDirection)) { |
454 | return switch (growthDirection) { |
455 | GrowthDirection.forward => GrowthDirection.reverse, |
456 | GrowthDirection.reverse => GrowthDirection.forward, |
457 | }; |
458 | } |
459 | return growthDirection; |
460 | } |
461 | |
462 | @override |
463 | bool get isTight => false; |
464 | |
465 | @override |
466 | bool get isNormalized { |
467 | return scrollOffset >= 0.0 && |
468 | crossAxisExtent >= 0.0 && |
469 | axisDirectionToAxis(axisDirection) != axisDirectionToAxis(crossAxisDirection) && |
470 | viewportMainAxisExtent >= 0.0 && |
471 | remainingPaintExtent >= 0.0; |
472 | } |
473 | |
474 | /// Returns [BoxConstraints] that reflects the sliver constraints. |
475 | /// |
476 | /// The `minExtent` and `maxExtent` are used as the constraints in the main |
477 | /// axis. If non-null, the given `crossAxisExtent` is used as a tight |
478 | /// constraint in the cross axis. Otherwise, the [crossAxisExtent] from this |
479 | /// object is used as a constraint in the cross axis. |
480 | /// |
481 | /// Useful for slivers that have [RenderBox] children. |
482 | BoxConstraints asBoxConstraints({ |
483 | double minExtent = 0.0, |
484 | double maxExtent = double.infinity, |
485 | double? crossAxisExtent, |
486 | }) { |
487 | crossAxisExtent ??= this.crossAxisExtent; |
488 | switch (axis) { |
489 | case Axis.horizontal: |
490 | return BoxConstraints( |
491 | minHeight: crossAxisExtent, |
492 | maxHeight: crossAxisExtent, |
493 | minWidth: minExtent, |
494 | maxWidth: maxExtent, |
495 | ); |
496 | case Axis.vertical: |
497 | return BoxConstraints( |
498 | minWidth: crossAxisExtent, |
499 | maxWidth: crossAxisExtent, |
500 | minHeight: minExtent, |
501 | maxHeight: maxExtent, |
502 | ); |
503 | } |
504 | } |
505 | |
506 | @override |
507 | bool debugAssertIsValid({ |
508 | bool isAppliedConstraint = false, |
509 | InformationCollector? informationCollector, |
510 | }) { |
511 | assert(() { |
512 | bool hasErrors = false; |
513 | final StringBuffer errorMessage = StringBuffer('\n'); |
514 | void verify(bool check, String message) { |
515 | if (check) { |
516 | return; |
517 | } |
518 | hasErrors = true; |
519 | errorMessage.writeln('$message '); |
520 | } |
521 | |
522 | void verifyDouble( |
523 | double property, |
524 | String name, { |
525 | bool mustBePositive = false, |
526 | bool mustBeNegative = false, |
527 | }) { |
528 | if (property.isNaN) { |
529 | String additional = '.'; |
530 | if (mustBePositive) { |
531 | additional = ', expected greater than or equal to zero.'; |
532 | } else if (mustBeNegative) { |
533 | additional = ', expected less than or equal to zero.'; |
534 | } |
535 | verify(false, 'The "$name " is NaN$additional '); |
536 | } else if (mustBePositive) { |
537 | verify(property >= 0.0, 'The "$name " is negative.'); |
538 | } else if (mustBeNegative) { |
539 | verify(property <= 0.0, 'The "$name " is positive.'); |
540 | } |
541 | } |
542 | |
543 | verifyDouble(scrollOffset, 'scrollOffset'); |
544 | verifyDouble(overlap, 'overlap'); |
545 | verifyDouble(crossAxisExtent, 'crossAxisExtent'); |
546 | verifyDouble(scrollOffset, 'scrollOffset', mustBePositive: true); |
547 | verify( |
548 | axisDirectionToAxis(axisDirection) != axisDirectionToAxis(crossAxisDirection), |
549 | 'The "axisDirection" and the "crossAxisDirection" are along the same axis.', |
550 | ); |
551 | verifyDouble(viewportMainAxisExtent, 'viewportMainAxisExtent', mustBePositive: true); |
552 | verifyDouble(remainingPaintExtent, 'remainingPaintExtent', mustBePositive: true); |
553 | verifyDouble(remainingCacheExtent, 'remainingCacheExtent', mustBePositive: true); |
554 | verifyDouble(cacheOrigin, 'cacheOrigin', mustBeNegative: true); |
555 | verifyDouble(precedingScrollExtent, 'precedingScrollExtent', mustBePositive: true); |
556 | verify( |
557 | isNormalized, |
558 | 'The constraints are not normalized.', |
559 | ); // should be redundant with earlier checks |
560 | if (hasErrors) { |
561 | throw FlutterError.fromParts(<DiagnosticsNode>[ |
562 | ErrorSummary('$runtimeType is not valid:$errorMessage '), |
563 | if (informationCollector != null) ...informationCollector(), |
564 | DiagnosticsProperty<SliverConstraints>( |
565 | 'The offending constraints were', |
566 | this, |
567 | style: DiagnosticsTreeStyle.errorProperty, |
568 | ), |
569 | ]); |
570 | } |
571 | return true; |
572 | }()); |
573 | return true; |
574 | } |
575 | |
576 | @override |
577 | bool operator ==(Object other) { |
578 | if (identical(this, other)) { |
579 | return true; |
580 | } |
581 | if (other is! SliverConstraints) { |
582 | return false; |
583 | } |
584 | assert(other.debugAssertIsValid()); |
585 | return other.axisDirection == axisDirection && |
586 | other.growthDirection == growthDirection && |
587 | other.userScrollDirection == userScrollDirection && |
588 | other.scrollOffset == scrollOffset && |
589 | other.precedingScrollExtent == precedingScrollExtent && |
590 | other.overlap == overlap && |
591 | other.remainingPaintExtent == remainingPaintExtent && |
592 | other.crossAxisExtent == crossAxisExtent && |
593 | other.crossAxisDirection == crossAxisDirection && |
594 | other.viewportMainAxisExtent == viewportMainAxisExtent && |
595 | other.remainingCacheExtent == remainingCacheExtent && |
596 | other.cacheOrigin == cacheOrigin; |
597 | } |
598 | |
599 | @override |
600 | int get hashCode => Object.hash( |
601 | axisDirection, |
602 | growthDirection, |
603 | userScrollDirection, |
604 | scrollOffset, |
605 | precedingScrollExtent, |
606 | overlap, |
607 | remainingPaintExtent, |
608 | crossAxisExtent, |
609 | crossAxisDirection, |
610 | viewportMainAxisExtent, |
611 | remainingCacheExtent, |
612 | cacheOrigin, |
613 | ); |
614 | |
615 | @override |
616 | String toString() { |
617 | final List<String> properties = <String>[ |
618 | '$axisDirection ', |
619 | '$growthDirection ', |
620 | '$userScrollDirection ', |
621 | 'scrollOffset:${scrollOffset.toStringAsFixed(1)} ', |
622 | 'precedingScrollExtent:${precedingScrollExtent.toStringAsFixed(1)} ', |
623 | 'remainingPaintExtent:${remainingPaintExtent.toStringAsFixed(1)} ', |
624 | if (overlap != 0.0) 'overlap:${overlap.toStringAsFixed(1)} ', |
625 | 'crossAxisExtent:${crossAxisExtent.toStringAsFixed(1)} ', |
626 | 'crossAxisDirection:$crossAxisDirection ', |
627 | 'viewportMainAxisExtent:${viewportMainAxisExtent.toStringAsFixed(1)} ', |
628 | 'remainingCacheExtent:${remainingCacheExtent.toStringAsFixed(1)} ', |
629 | 'cacheOrigin:${cacheOrigin.toStringAsFixed(1)} ', |
630 | ]; |
631 | return 'SliverConstraints(${properties.join( ', ')} )'; |
632 | } |
633 | } |
634 | |
635 | /// Describes the amount of space occupied by a [RenderSliver]. |
636 | /// |
637 | /// A sliver can occupy space in several different ways, which is why this class |
638 | /// contains multiple values. |
639 | @immutable |
640 | class SliverGeometry with Diagnosticable { |
641 | /// Creates an object that describes the amount of space occupied by a sliver. |
642 | /// |
643 | /// If the [layoutExtent] argument is null, [layoutExtent] defaults to the |
644 | /// [paintExtent]. If the [hitTestExtent] argument is null, [hitTestExtent] |
645 | /// defaults to the [paintExtent]. If [visible] is null, [visible] defaults to |
646 | /// whether [paintExtent] is greater than zero. |
647 | const SliverGeometry({ |
648 | this.scrollExtent = 0.0, |
649 | this.paintExtent = 0.0, |
650 | this.paintOrigin = 0.0, |
651 | double? layoutExtent, |
652 | this.maxPaintExtent = 0.0, |
653 | this.maxScrollObstructionExtent = 0.0, |
654 | this.crossAxisExtent, |
655 | double? hitTestExtent, |
656 | bool? visible, |
657 | this.hasVisualOverflow = false, |
658 | this.scrollOffsetCorrection, |
659 | double? cacheExtent, |
660 | }) : assert(scrollOffsetCorrection != 0.0), |
661 | layoutExtent = layoutExtent ?? paintExtent, |
662 | hitTestExtent = hitTestExtent ?? paintExtent, |
663 | cacheExtent = cacheExtent ?? layoutExtent ?? paintExtent, |
664 | visible = visible ?? paintExtent > 0.0; |
665 | |
666 | /// Creates a copy of this object but with the given fields replaced with the |
667 | /// new values. |
668 | SliverGeometry copyWith({ |
669 | double? scrollExtent, |
670 | double? paintExtent, |
671 | double? paintOrigin, |
672 | double? layoutExtent, |
673 | double? maxPaintExtent, |
674 | double? maxScrollObstructionExtent, |
675 | double? crossAxisExtent, |
676 | double? hitTestExtent, |
677 | bool? visible, |
678 | bool? hasVisualOverflow, |
679 | double? cacheExtent, |
680 | }) { |
681 | return SliverGeometry( |
682 | scrollExtent: scrollExtent ?? this.scrollExtent, |
683 | paintExtent: paintExtent ?? this.paintExtent, |
684 | paintOrigin: paintOrigin ?? this.paintOrigin, |
685 | layoutExtent: layoutExtent ?? this.layoutExtent, |
686 | maxPaintExtent: maxPaintExtent ?? this.maxPaintExtent, |
687 | maxScrollObstructionExtent: maxScrollObstructionExtent ?? this.maxScrollObstructionExtent, |
688 | crossAxisExtent: crossAxisExtent ?? this.crossAxisExtent, |
689 | hitTestExtent: hitTestExtent ?? this.hitTestExtent, |
690 | visible: visible ?? this.visible, |
691 | hasVisualOverflow: hasVisualOverflow ?? this.hasVisualOverflow, |
692 | cacheExtent: cacheExtent ?? this.cacheExtent, |
693 | ); |
694 | } |
695 | |
696 | /// A sliver that occupies no space at all. |
697 | static const SliverGeometry zero = SliverGeometry(); |
698 | |
699 | /// The (estimated) total scrollable extent that this sliver has content for. |
700 | /// |
701 | /// This is the amount of scrolling the user needs to do to get from the |
702 | /// beginning of this sliver to the end of this sliver. |
703 | /// |
704 | /// The value is used to calculate the [SliverConstraints.scrollOffset] of |
705 | /// all slivers in the scrollable and thus should be provided whether the |
706 | /// sliver is currently in the viewport or not. |
707 | /// |
708 | /// In a typical scrolling scenario, the [scrollExtent] is constant for a |
709 | /// sliver throughout the scrolling while [paintExtent] and [layoutExtent] |
710 | /// will progress from `0` when offscreen to between `0` and [scrollExtent] |
711 | /// as the sliver scrolls partially into and out of the screen and is |
712 | /// equal to [scrollExtent] while the sliver is entirely on screen. However, |
713 | /// these relationships can be customized to achieve more special effects. |
714 | /// |
715 | /// This value must be accurate if the [paintExtent] is less than the |
716 | /// [SliverConstraints.remainingPaintExtent] provided during layout. |
717 | final double scrollExtent; |
718 | |
719 | /// The visual location of the first visible part of this sliver relative to |
720 | /// its layout position. |
721 | /// |
722 | /// For example, if the sliver wishes to paint visually before its layout |
723 | /// position, the [paintOrigin] is negative. The coordinate system this sliver |
724 | /// uses for painting is relative to this [paintOrigin]. In other words, |
725 | /// when [RenderSliver.paint] is called, the (0, 0) position of the [Offset] |
726 | /// given to it is at this [paintOrigin]. |
727 | /// |
728 | /// The coordinate system used for the [paintOrigin] itself is relative |
729 | /// to the start of this sliver's layout position rather than relative to |
730 | /// its current position on the viewport. In other words, in a typical |
731 | /// scrolling scenario, [paintOrigin] remains constant at 0.0 rather than |
732 | /// tracking from 0.0 to [SliverConstraints.viewportMainAxisExtent] as the |
733 | /// sliver scrolls past the viewport. |
734 | /// |
735 | /// This value does not affect the layout of subsequent slivers. The next |
736 | /// sliver is still placed at [layoutExtent] after this sliver's layout |
737 | /// position. This value does affect where the [paintExtent] extent is |
738 | /// measured from when computing the [SliverConstraints.overlap] for the next |
739 | /// sliver. |
740 | /// |
741 | /// Defaults to 0.0, which means slivers start painting at their layout |
742 | /// position by default. |
743 | final double paintOrigin; |
744 | |
745 | /// The amount of currently visible visual space that was taken by the sliver |
746 | /// to render the subset of the sliver that covers all or part of the |
747 | /// [SliverConstraints.remainingPaintExtent] in the current viewport. |
748 | /// |
749 | /// This value does not affect how the next sliver is positioned. In other |
750 | /// words, if this value was 100 and [layoutExtent] was 0, typical slivers |
751 | /// placed after it would end up drawing in the same 100 pixel space while |
752 | /// painting. |
753 | /// |
754 | /// This must be between zero and [SliverConstraints.remainingPaintExtent]. |
755 | /// |
756 | /// This value is typically 0 when outside of the viewport and grows or |
757 | /// shrinks from 0 or to 0 as the sliver is being scrolled into and out of the |
758 | /// viewport unless the sliver wants to achieve a special effect and paint |
759 | /// even when scrolled away. |
760 | /// |
761 | /// This contributes to the calculation for the next sliver's |
762 | /// [SliverConstraints.overlap]. |
763 | final double paintExtent; |
764 | |
765 | /// The distance from the first visible part of this sliver to the first |
766 | /// visible part of the next sliver, assuming the next sliver's |
767 | /// [SliverConstraints.scrollOffset] is zero. |
768 | /// |
769 | /// This must be between zero and [paintExtent]. It defaults to [paintExtent]. |
770 | /// |
771 | /// This value is typically 0 when outside of the viewport and grows or |
772 | /// shrinks from 0 or to 0 as the sliver is being scrolled into and out of the |
773 | /// viewport unless the sliver wants to achieve a special effect and push |
774 | /// down the layout start position of subsequent slivers before the sliver is |
775 | /// even scrolled into the viewport. |
776 | final double layoutExtent; |
777 | |
778 | /// The (estimated) total paint extent that this sliver would be able to |
779 | /// provide if the [SliverConstraints.remainingPaintExtent] was infinite. |
780 | /// |
781 | /// This is used by viewports that implement shrink-wrapping. |
782 | /// |
783 | /// By definition, this cannot be less than [paintExtent]. |
784 | final double maxPaintExtent; |
785 | |
786 | /// The maximum extent by which this sliver can reduce the area in which |
787 | /// content can scroll if the sliver were pinned at the edge. |
788 | /// |
789 | /// Slivers that never get pinned at the edge, should return zero. |
790 | /// |
791 | /// A pinned app bar is an example for a sliver that would use this setting: |
792 | /// When the app bar is pinned to the top, the area in which content can |
793 | /// actually scroll is reduced by the height of the app bar. |
794 | final double maxScrollObstructionExtent; |
795 | |
796 | /// The distance from where this sliver started painting to the bottom of |
797 | /// where it should accept hits. |
798 | /// |
799 | /// This must be between zero and [paintExtent]. It defaults to [paintExtent]. |
800 | final double hitTestExtent; |
801 | |
802 | /// Whether this sliver should be painted. |
803 | /// |
804 | /// By default, this is true if [paintExtent] is greater than zero, and |
805 | /// false if [paintExtent] is zero. |
806 | final bool visible; |
807 | |
808 | /// Whether this sliver has visual overflow. |
809 | /// |
810 | /// By default, this is false, which means the viewport does not need to clip |
811 | /// its children. If any slivers have visual overflow, the viewport will apply |
812 | /// a clip to its children. |
813 | final bool hasVisualOverflow; |
814 | |
815 | /// If this is non-zero after [RenderSliver.performLayout] returns, the scroll |
816 | /// offset will be adjusted by the parent and then the entire layout of the |
817 | /// parent will be rerun. |
818 | /// |
819 | /// When the value is non-zero, the [RenderSliver] does not need to compute |
820 | /// the rest of the values when constructing the [SliverGeometry] or call |
821 | /// [RenderObject.layout] on its children since [RenderSliver.performLayout] |
822 | /// will be called again on this sliver in the same frame after the |
823 | /// [SliverConstraints.scrollOffset] correction has been applied, when the |
824 | /// proper [SliverGeometry] and layout of its children can be computed. |
825 | /// |
826 | /// If the parent is also a [RenderSliver], it must propagate this value |
827 | /// in its own [RenderSliver.geometry] property until a viewport which adjusts |
828 | /// its offset based on this value. |
829 | final double? scrollOffsetCorrection; |
830 | |
831 | /// How many pixels the sliver has consumed in the |
832 | /// [SliverConstraints.remainingCacheExtent]. |
833 | /// |
834 | /// This value should be equal to or larger than the [layoutExtent] because |
835 | /// the sliver always consumes at least the [layoutExtent] from the |
836 | /// [SliverConstraints.remainingCacheExtent] and possibly more if it falls |
837 | /// into the cache area of the viewport. |
838 | /// |
839 | /// See also: |
840 | /// |
841 | /// * [RenderViewport.cacheExtent] for a description of a viewport's cache area. |
842 | final double cacheExtent; |
843 | |
844 | /// The amount of space allocated to the cross axis. |
845 | /// |
846 | /// This value will be typically null unless it is different from |
847 | /// [SliverConstraints.crossAxisExtent]. If null, then the cross axis extent of |
848 | /// the sliver is assumed to be the same as the [SliverConstraints.crossAxisExtent]. |
849 | /// This is because slivers typically consume all of the extent that is available |
850 | /// in the cross axis. |
851 | /// |
852 | /// See also: |
853 | /// |
854 | /// * [SliverConstrainedCrossAxis] for an example of a sliver which takes up |
855 | /// a smaller cross axis extent than the provided constraint. |
856 | /// * [SliverCrossAxisGroup] for an example of a sliver which makes use of this |
857 | /// [crossAxisExtent] to lay out their children. |
858 | final double? crossAxisExtent; |
859 | |
860 | /// Asserts that this geometry is internally consistent. |
861 | /// |
862 | /// Does nothing if asserts are disabled. Always returns true. |
863 | bool debugAssertIsValid({InformationCollector? informationCollector}) { |
864 | assert(() { |
865 | void verify(bool check, String summary, {List<DiagnosticsNode>? details}) { |
866 | if (check) { |
867 | return; |
868 | } |
869 | throw FlutterError.fromParts(<DiagnosticsNode>[ |
870 | ErrorSummary('${objectRuntimeType(this, 'SliverGeometry')} is not valid:$summary '), |
871 | ...?details, |
872 | if (informationCollector != null) ...informationCollector(), |
873 | ]); |
874 | } |
875 | |
876 | verify(scrollExtent >= 0.0, 'The "scrollExtent" is negative.'); |
877 | verify(paintExtent >= 0.0, 'The "paintExtent" is negative.'); |
878 | verify(layoutExtent >= 0.0, 'The "layoutExtent" is negative.'); |
879 | verify(cacheExtent >= 0.0, 'The "cacheExtent" is negative.'); |
880 | if (layoutExtent > paintExtent) { |
881 | verify( |
882 | false, |
883 | 'The "layoutExtent" exceeds the "paintExtent".', |
884 | details: _debugCompareFloats('paintExtent', paintExtent, 'layoutExtent', layoutExtent), |
885 | ); |
886 | } |
887 | // If the paintExtent is slightly more than the maxPaintExtent, but the difference is still less |
888 | // than precisionErrorTolerance, we will not throw the assert below. |
889 | if (paintExtent - maxPaintExtent > precisionErrorTolerance) { |
890 | verify( |
891 | false, |
892 | 'The "maxPaintExtent" is less than the "paintExtent".', |
893 | details: _debugCompareFloats('maxPaintExtent', maxPaintExtent, 'paintExtent', paintExtent) |
894 | ..add( |
895 | ErrorDescription( |
896 | "By definition, a sliver can't paint more than the maximum that it can paint!", |
897 | ), |
898 | ), |
899 | ); |
900 | } |
901 | verify(hitTestExtent >= 0.0, 'The "hitTestExtent" is negative.'); |
902 | verify(scrollOffsetCorrection != 0.0, 'The "scrollOffsetCorrection" is zero.'); |
903 | return true; |
904 | }()); |
905 | return true; |
906 | } |
907 | |
908 | @override |
909 | String toStringShort() => objectRuntimeType(this, 'SliverGeometry'); |
910 | |
911 | @override |
912 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
913 | super.debugFillProperties(properties); |
914 | properties.add(DoubleProperty('scrollExtent', scrollExtent)); |
915 | if (paintExtent > 0.0) { |
916 | properties.add( |
917 | DoubleProperty('paintExtent', paintExtent, unit: visible ? null : ' but not painting'), |
918 | ); |
919 | } else if (paintExtent == 0.0) { |
920 | if (visible) { |
921 | properties.add( |
922 | DoubleProperty('paintExtent', paintExtent, unit: visible ? null : ' but visible'), |
923 | ); |
924 | } |
925 | properties.add(FlagProperty('visible', value: visible, ifFalse: 'hidden')); |
926 | } else { |
927 | // Negative paintExtent! |
928 | properties.add(DoubleProperty('paintExtent', paintExtent, tooltip: '!')); |
929 | } |
930 | properties.add(DoubleProperty('paintOrigin', paintOrigin, defaultValue: 0.0)); |
931 | properties.add(DoubleProperty('layoutExtent', layoutExtent, defaultValue: paintExtent)); |
932 | properties.add(DoubleProperty('maxPaintExtent', maxPaintExtent)); |
933 | properties.add(DoubleProperty('hitTestExtent', hitTestExtent, defaultValue: paintExtent)); |
934 | properties.add( |
935 | DiagnosticsProperty<bool>('hasVisualOverflow', hasVisualOverflow, defaultValue: false), |
936 | ); |
937 | properties.add( |
938 | DoubleProperty('scrollOffsetCorrection', scrollOffsetCorrection, defaultValue: null), |
939 | ); |
940 | properties.add(DoubleProperty('cacheExtent', cacheExtent, defaultValue: 0.0)); |
941 | } |
942 | } |
943 | |
944 | /// Method signature for hit testing a [RenderSliver]. |
945 | /// |
946 | /// Used by [SliverHitTestResult.addWithAxisOffset] to hit test [RenderSliver] |
947 | /// children. |
948 | /// |
949 | /// See also: |
950 | /// |
951 | /// * [RenderSliver.hitTest], which documents more details around hit testing |
952 | /// [RenderSliver]s. |
953 | typedef SliverHitTest = |
954 | bool Function( |
955 | SliverHitTestResult result, { |
956 | required double mainAxisPosition, |
957 | required double crossAxisPosition, |
958 | }); |
959 | |
960 | /// The result of performing a hit test on [RenderSliver]s. |
961 | /// |
962 | /// An instance of this class is provided to [RenderSliver.hitTest] to record |
963 | /// the result of the hit test. |
964 | class SliverHitTestResult extends HitTestResult { |
965 | /// Creates an empty hit test result for hit testing on [RenderSliver]. |
966 | SliverHitTestResult() : super(); |
967 | |
968 | /// Wraps `result` to create a [HitTestResult] that implements the |
969 | /// [SliverHitTestResult] protocol for hit testing on [RenderSliver]s. |
970 | /// |
971 | /// This method is used by [RenderObject]s that adapt between the |
972 | /// [RenderSliver]-world and the non-[RenderSliver]-world to convert a |
973 | /// (subtype of) [HitTestResult] to a [SliverHitTestResult] for hit testing on |
974 | /// [RenderSliver]s. |
975 | /// |
976 | /// The [HitTestEntry] instances added to the returned [SliverHitTestResult] |
977 | /// are also added to the wrapped `result` (both share the same underlying |
978 | /// data structure to store [HitTestEntry] instances). |
979 | /// |
980 | /// See also: |
981 | /// |
982 | /// * [HitTestResult.wrap], which turns a [SliverHitTestResult] back into a |
983 | /// generic [HitTestResult]. |
984 | /// * [BoxHitTestResult.wrap], which turns a [SliverHitTestResult] into a |
985 | /// [BoxHitTestResult] for hit testing on [RenderBox] children. |
986 | SliverHitTestResult.wrap(super.result) : super.wrap(); |
987 | |
988 | /// Transforms `mainAxisPosition` and `crossAxisPosition` to the local |
989 | /// coordinate system of a child for hit-testing the child. |
990 | /// |
991 | /// The actual hit testing of the child needs to be implemented in the |
992 | /// provided `hitTest` callback, which is invoked with the transformed |
993 | /// `position` as argument. |
994 | /// |
995 | /// For the transform `mainAxisOffset` is subtracted from `mainAxisPosition` |
996 | /// and `crossAxisOffset` is subtracted from `crossAxisPosition`. |
997 | /// |
998 | /// The `paintOffset` describes how the paint position of a point painted at |
999 | /// the provided `mainAxisPosition` and `crossAxisPosition` would change after |
1000 | /// `mainAxisOffset` and `crossAxisOffset` have been applied. This |
1001 | /// `paintOffset` is used to properly convert [PointerEvent]s to the local |
1002 | /// coordinate system of the event receiver. |
1003 | /// |
1004 | /// The `paintOffset` may be null if `mainAxisOffset` and `crossAxisOffset` are |
1005 | /// both zero. |
1006 | /// |
1007 | /// The function returns the return value of `hitTest`. |
1008 | bool addWithAxisOffset({ |
1009 | required Offset? paintOffset, |
1010 | required double mainAxisOffset, |
1011 | required double crossAxisOffset, |
1012 | required double mainAxisPosition, |
1013 | required double crossAxisPosition, |
1014 | required SliverHitTest hitTest, |
1015 | }) { |
1016 | if (paintOffset != null) { |
1017 | pushOffset(-paintOffset); |
1018 | } |
1019 | final bool isHit = hitTest( |
1020 | this, |
1021 | mainAxisPosition: mainAxisPosition - mainAxisOffset, |
1022 | crossAxisPosition: crossAxisPosition - crossAxisOffset, |
1023 | ); |
1024 | if (paintOffset != null) { |
1025 | popTransform(); |
1026 | } |
1027 | return isHit; |
1028 | } |
1029 | } |
1030 | |
1031 | /// A hit test entry used by [RenderSliver]. |
1032 | /// |
1033 | /// The coordinate system used by this hit test entry is relative to the |
1034 | /// [AxisDirection] of the target sliver. |
1035 | class SliverHitTestEntry extends HitTestEntry<RenderSliver> { |
1036 | /// Creates a sliver hit test entry. |
1037 | SliverHitTestEntry( |
1038 | super.target, { |
1039 | required this.mainAxisPosition, |
1040 | required this.crossAxisPosition, |
1041 | }); |
1042 | |
1043 | /// The distance in the [AxisDirection] from the edge of the sliver's painted |
1044 | /// area (as given by the [SliverConstraints.scrollOffset]) to the hit point. |
1045 | /// This can be an unusual direction, for example in the [AxisDirection.up] |
1046 | /// case this is a distance from the _bottom_ of the sliver's painted area. |
1047 | final double mainAxisPosition; |
1048 | |
1049 | /// The distance to the hit point in the axis opposite the |
1050 | /// [SliverConstraints.axis]. |
1051 | /// |
1052 | /// If the cross axis is horizontal (i.e. the |
1053 | /// [SliverConstraints.axisDirection] is either [AxisDirection.down] or |
1054 | /// [AxisDirection.up]), then the [crossAxisPosition] is a distance from the |
1055 | /// left edge of the sliver. If the cross axis is vertical (i.e. the |
1056 | /// [SliverConstraints.axisDirection] is either [AxisDirection.right] or |
1057 | /// [AxisDirection.left]), then the [crossAxisPosition] is a distance from the |
1058 | /// top edge of the sliver. |
1059 | /// |
1060 | /// This is always a distance from the left or top of the parent, never a |
1061 | /// distance from the right or bottom. |
1062 | final double crossAxisPosition; |
1063 | |
1064 | @override |
1065 | String toString() => |
1066 | '${target.runtimeType} @(mainAxis:$mainAxisPosition , crossAxis:$crossAxisPosition )'; |
1067 | } |
1068 | |
1069 | /// Parent data structure used by parents of slivers that position their |
1070 | /// children using layout offsets. |
1071 | /// |
1072 | /// This data structure is optimized for fast layout. It is best used by parents |
1073 | /// that expect to have many children whose relative positions don't change even |
1074 | /// when the scroll offset does. |
1075 | class SliverLogicalParentData extends ParentData { |
1076 | /// The position of the child relative to the zero scroll offset. |
1077 | /// |
1078 | /// The number of pixels from the zero scroll offset of the parent sliver |
1079 | /// (the line at which its [SliverConstraints.scrollOffset] is zero) to the |
1080 | /// side of the child closest to that offset. A [layoutOffset] can be null |
1081 | /// when it cannot be determined. The value will be set after layout. |
1082 | /// |
1083 | /// In a typical list, this does not change as the parent is scrolled. |
1084 | /// |
1085 | /// Defaults to null. |
1086 | double? layoutOffset; |
1087 | |
1088 | @override |
1089 | String toString() => |
1090 | 'layoutOffset=${layoutOffset == null ? 'None': layoutOffset!.toStringAsFixed(1)} '; |
1091 | } |
1092 | |
1093 | /// Parent data for slivers that have multiple children and that position their |
1094 | /// children using layout offsets. |
1095 | class SliverLogicalContainerParentData extends SliverLogicalParentData |
1096 | with ContainerParentDataMixin<RenderSliver> {} |
1097 | |
1098 | /// Parent data structure used by parents of slivers that position their |
1099 | /// children using absolute coordinates. |
1100 | /// |
1101 | /// For example, used by [RenderViewport]. |
1102 | /// |
1103 | /// This data structure is optimized for fast painting, at the cost of requiring |
1104 | /// additional work during layout when the children change their offsets. It is |
1105 | /// best used by parents that expect to have few children, especially if those |
1106 | /// children will themselves be very tall relative to the parent. |
1107 | class SliverPhysicalParentData extends ParentData { |
1108 | /// The position of the child relative to the parent. |
1109 | /// |
1110 | /// This is the distance from the top left visible corner of the parent to the |
1111 | /// top left visible corner of the sliver. |
1112 | Offset paintOffset = Offset.zero; |
1113 | |
1114 | /// The [crossAxisFlex] factor to use for this sliver child. |
1115 | /// |
1116 | /// If used outside of a [SliverCrossAxisGroup] widget, this value has no meaning. |
1117 | /// |
1118 | /// If null or zero, the child is inflexible and determines its own size in the cross axis. |
1119 | /// If non-zero, the amount of space the child can occupy in the cross axis is |
1120 | /// determined by dividing the free space (after placing the inflexible children) |
1121 | /// according to the flex factors of the flexible children. |
1122 | /// |
1123 | /// This value is only used by the [SliverCrossAxisGroup] widget to determine |
1124 | /// how to allocate its [SliverConstraints.crossAxisExtent] to its children. |
1125 | /// |
1126 | /// See also: |
1127 | /// |
1128 | /// * [SliverCrossAxisGroup], which lays out multiple slivers along the |
1129 | /// cross axis direction. |
1130 | int? crossAxisFlex; |
1131 | |
1132 | /// Apply the [paintOffset] to the given [transform]. |
1133 | /// |
1134 | /// Used to implement [RenderObject.applyPaintTransform] by slivers that use |
1135 | /// [SliverPhysicalParentData]. |
1136 | void applyPaintTransform(Matrix4 transform) { |
1137 | // Hit test logic relies on this always providing an invertible matrix. |
1138 | transform.translate(paintOffset.dx, paintOffset.dy); |
1139 | } |
1140 | |
1141 | @override |
1142 | String toString() => 'paintOffset=$paintOffset '; |
1143 | } |
1144 | |
1145 | /// Parent data for slivers that have multiple children and that position their |
1146 | /// children using absolute coordinates. |
1147 | class SliverPhysicalContainerParentData extends SliverPhysicalParentData |
1148 | with ContainerParentDataMixin<RenderSliver> {} |
1149 | |
1150 | List<DiagnosticsNode> _debugCompareFloats( |
1151 | String labelA, |
1152 | double valueA, |
1153 | String labelB, |
1154 | double valueB, |
1155 | ) { |
1156 | return <DiagnosticsNode>[ |
1157 | if (valueA.toStringAsFixed(1) != valueB.toStringAsFixed(1)) |
1158 | ErrorDescription( |
1159 | 'The$labelA is${valueA.toStringAsFixed(1)} , but ' |
1160 | 'the$labelB is${valueB.toStringAsFixed(1)} .', |
1161 | ) |
1162 | else ...<DiagnosticsNode>[ |
1163 | ErrorDescription('The$labelA is$valueA , but the$labelB is$valueB .'), |
1164 | ErrorHint( |
1165 | 'Maybe you have fallen prey to floating point rounding errors, and should explicitly ' |
1166 | 'apply the min() or max() functions, or the clamp() method, to the$labelB ?', |
1167 | ), |
1168 | ], |
1169 | ]; |
1170 | } |
1171 | |
1172 | /// Base class for the render objects that implement scroll effects in viewports. |
1173 | /// |
1174 | /// A [RenderViewport] has a list of child slivers. Each sliver — literally a |
1175 | /// slice of the viewport's contents — is laid out in turn, covering the |
1176 | /// viewport in the process. (Every sliver is laid out each time, including |
1177 | /// those that have zero extent because they are "scrolled off" or are beyond |
1178 | /// the end of the viewport.) |
1179 | /// |
1180 | /// Slivers participate in the _sliver protocol_, wherein during [layout] each |
1181 | /// sliver receives a [SliverConstraints] object and computes a corresponding |
1182 | /// [SliverGeometry] that describes where it fits in the viewport. This is |
1183 | /// analogous to the box protocol used by [RenderBox], which gets a |
1184 | /// [BoxConstraints] as input and computes a [Size]. |
1185 | /// |
1186 | /// Slivers have a leading edge, which is where the position described by |
1187 | /// [SliverConstraints.scrollOffset] for this sliver begins. Slivers have |
1188 | /// several dimensions, the primary of which is [SliverGeometry.paintExtent], |
1189 | /// which describes the extent of the sliver along the main axis, starting from |
1190 | /// the leading edge, reaching either the end of the viewport or the end of the |
1191 | /// sliver, whichever comes first. |
1192 | /// |
1193 | /// Slivers can change dimensions based on the changing constraints in a |
1194 | /// non-linear fashion, to achieve various scroll effects. For example, the |
1195 | /// various [RenderSliverPersistentHeader] subclasses, on which [SliverAppBar] |
1196 | /// is based, achieve effects such as staying visible despite the scroll offset, |
1197 | /// or reappearing at different offsets based on the user's scroll direction |
1198 | /// ([SliverConstraints.userScrollDirection]). |
1199 | /// |
1200 | /// {@youtube 560 315 https://www.youtube.com/watch?v=Mz3kHQxBjGg} |
1201 | /// |
1202 | /// ## Writing a RenderSliver subclass |
1203 | /// |
1204 | /// Slivers can have sliver children, or children from another coordinate |
1205 | /// system, typically box children. (For details on the box protocol, see |
1206 | /// [RenderBox].) Slivers can also have different child models, typically having |
1207 | /// either one child, or a list of children. |
1208 | /// |
1209 | /// ### Examples of slivers |
1210 | /// |
1211 | /// A good example of a sliver with a single child that is also itself a sliver |
1212 | /// is [RenderSliverPadding], which indents its child. A sliver-to-sliver render |
1213 | /// object such as this must construct a [SliverConstraints] object for its |
1214 | /// child, then must take its child's [SliverGeometry] and use it to form its |
1215 | /// own [geometry]. |
1216 | /// |
1217 | /// The other common kind of one-child sliver is a sliver that has a single |
1218 | /// [RenderBox] child. An example of that would be [RenderSliverToBoxAdapter], |
1219 | /// which lays out a single box and sizes itself around the box. Such a sliver |
1220 | /// must use its [SliverConstraints] to create a [BoxConstraints] for the |
1221 | /// child, lay the child out (using the child's [layout] method), and then use |
1222 | /// the child's [RenderBox.size] to generate the sliver's [SliverGeometry]. |
1223 | /// |
1224 | /// The most common kind of sliver though is one with multiple children. The |
1225 | /// most straight-forward example of this is [RenderSliverList], which arranges |
1226 | /// its children one after the other in the main axis direction. As with the |
1227 | /// one-box-child sliver case, it uses its [constraints] to create a |
1228 | /// [BoxConstraints] for the children, and then it uses the aggregate |
1229 | /// information from all its children to generate its [geometry]. Unlike the |
1230 | /// one-child cases, however, it is judicious in which children it actually lays |
1231 | /// out (and later paints). If the scroll offset is 1000 pixels, and it |
1232 | /// previously determined that the first three children are each 400 pixels |
1233 | /// tall, then it will skip the first two and start the layout with its third |
1234 | /// child. |
1235 | /// |
1236 | /// ### Layout |
1237 | /// |
1238 | /// As they are laid out, slivers decide their [geometry], which includes their |
1239 | /// size ([SliverGeometry.paintExtent]) and the position of the next sliver |
1240 | /// ([SliverGeometry.layoutExtent]), as well as the position of each of their |
1241 | /// children, based on the input [constraints] from the viewport such as the |
1242 | /// scroll offset ([SliverConstraints.scrollOffset]). |
1243 | /// |
1244 | /// For example, a sliver that just paints a box 100 pixels high would say its |
1245 | /// [SliverGeometry.paintExtent] was 100 pixels when the scroll offset was zero, |
1246 | /// but would say its [SliverGeometry.paintExtent] was 25 pixels when the scroll |
1247 | /// offset was 75 pixels, and would say it was zero when the scroll offset was |
1248 | /// 100 pixels or more. (This is assuming that |
1249 | /// [SliverConstraints.remainingPaintExtent] was more than 100 pixels.) |
1250 | /// |
1251 | /// The various dimensions that are provided as input to this system are in the |
1252 | /// [constraints]. They are described in detail in the documentation for the |
1253 | /// [SliverConstraints] class. |
1254 | /// |
1255 | /// The [performLayout] function must take these [constraints] and create a |
1256 | /// [SliverGeometry] object that it must then assign to the [geometry] property. |
1257 | /// The different dimensions of the geometry that can be configured are |
1258 | /// described in detail in the documentation for the [SliverGeometry] class. |
1259 | /// |
1260 | /// ### Painting |
1261 | /// |
1262 | /// In addition to implementing layout, a sliver must also implement painting. |
1263 | /// This is achieved by overriding the [paint] method. |
1264 | /// |
1265 | /// The [paint] method is called with an [Offset] from the [Canvas] origin to |
1266 | /// the top-left corner of the sliver, _regardless of the axis direction_. |
1267 | /// |
1268 | /// Subclasses should also override [applyPaintTransform] to provide the |
1269 | /// [Matrix4] describing the position of each child relative to the sliver. |
1270 | /// (This is used by, among other things, the accessibility layer, to determine |
1271 | /// the bounds of the child.) |
1272 | /// |
1273 | /// ### Hit testing |
1274 | /// |
1275 | /// To implement hit testing, either override the [hitTestSelf] and |
1276 | /// [hitTestChildren] methods, or, for more complex cases, instead override the |
1277 | /// [hitTest] method directly. |
1278 | /// |
1279 | /// To actually react to pointer events, the [handleEvent] method may be |
1280 | /// implemented. By default it does nothing. (Typically gestures are handled by |
1281 | /// widgets in the box protocol, not by slivers directly.) |
1282 | /// |
1283 | /// ### Helper methods |
1284 | /// |
1285 | /// There are a number of methods that a sliver should implement which will make |
1286 | /// the other methods easier to implement. Each method listed below has detailed |
1287 | /// documentation. In addition, the [RenderSliverHelpers] class can be used to |
1288 | /// mix in some helpful methods. |
1289 | /// |
1290 | /// #### childScrollOffset |
1291 | /// |
1292 | /// If the subclass positions children anywhere other than at scroll offset |
1293 | /// zero, it should override [childScrollOffset]. For example, |
1294 | /// [RenderSliverList] and [RenderSliverGrid] override this method, but |
1295 | /// [RenderSliverToBoxAdapter] does not. |
1296 | /// |
1297 | /// This is used by, among other things, [Scrollable.ensureVisible]. |
1298 | /// |
1299 | /// #### childMainAxisPosition |
1300 | /// |
1301 | /// Subclasses should implement [childMainAxisPosition] to describe where their |
1302 | /// children are positioned. |
1303 | /// |
1304 | /// #### childCrossAxisPosition |
1305 | /// |
1306 | /// If the subclass positions children in the cross-axis at a position other |
1307 | /// than zero, then it should override [childCrossAxisPosition]. For example |
1308 | /// [RenderSliverGrid] overrides this method. |
1309 | abstract class RenderSliver extends RenderObject { |
1310 | /// Whether this sliver should be included in the semantics tree. |
1311 | /// |
1312 | /// This value is used by [RenderViewportBase] to ensure a sliver is |
1313 | /// included in the semantics tree regardless of its geometry. |
1314 | /// |
1315 | /// A [RenderSliver] should override this value to `true` to ensure |
1316 | /// its child is included in the semantics tree. For example if your |
1317 | /// sliver is under a [RenderViewport] you may want to wrap it with |
1318 | /// a [SliverEnsureSemantics] to ensure that: |
1319 | /// |
1320 | /// 1. It is still visited by [RenderViewportBase.visitChildrenForSemantics] |
1321 | /// regardless of its geometry. This includes cases where your sliver is outside |
1322 | /// the current viewport and cache extent. |
1323 | /// 2. Its semantic information is not clipped out by the [RenderViewport] in |
1324 | /// [RenderViewportBase.describeSemanticsClip] or [RenderViewportBase.describeApproximatePaintClip]. |
1325 | /// |
1326 | /// If a given [RenderSliver] does not provide a valid [semanticBounds] it will still |
1327 | /// be dropped from the semantics tree. |
1328 | /// |
1329 | /// Defaults to `false`. |
1330 | bool get ensureSemantics => false; |
1331 | |
1332 | // layout input |
1333 | @override |
1334 | SliverConstraints get constraints => super.constraints as SliverConstraints; |
1335 | |
1336 | /// The amount of space this sliver occupies. |
1337 | /// |
1338 | /// This value is stale whenever this object is marked as needing layout. |
1339 | /// During [performLayout], do not read the [geometry] of a child unless you |
1340 | /// pass true for parentUsesSize when calling the child's [layout] function. |
1341 | /// |
1342 | /// The geometry of a sliver should be set only during the sliver's |
1343 | /// [performLayout] or [performResize] functions. If you wish to change the |
1344 | /// geometry of a sliver outside of those functions, call [markNeedsLayout] |
1345 | /// instead to schedule a layout of the sliver. |
1346 | SliverGeometry? get geometry => _geometry; |
1347 | SliverGeometry? _geometry; |
1348 | set geometry(SliverGeometry? value) { |
1349 | assert(!(debugDoingThisResize && debugDoingThisLayout)); |
1350 | assert(sizedByParent || !debugDoingThisResize); |
1351 | assert(() { |
1352 | if ((sizedByParent && debugDoingThisResize) || (!sizedByParent && debugDoingThisLayout)) { |
1353 | return true; |
1354 | } |
1355 | assert(!debugDoingThisResize); |
1356 | DiagnosticsNode? contract, violation, hint; |
1357 | if (debugDoingThisLayout) { |
1358 | assert(sizedByParent); |
1359 | violation = ErrorDescription( |
1360 | 'It appears that the geometry setter was called from performLayout().', |
1361 | ); |
1362 | } else { |
1363 | violation = ErrorDescription( |
1364 | 'The geometry setter was called from outside layout (neither performResize() nor performLayout() were being run for this object).', |
1365 | ); |
1366 | if (owner != null && owner!.debugDoingLayout) { |
1367 | hint = ErrorDescription( |
1368 | 'Only the object itself can set its geometry. It is a contract violation for other objects to set it.', |
1369 | ); |
1370 | } |
1371 | } |
1372 | if (sizedByParent) { |
1373 | contract = ErrorDescription( |
1374 | 'Because this RenderSliver has sizedByParent set to true, it must set its geometry in performResize().', |
1375 | ); |
1376 | } else { |
1377 | contract = ErrorDescription( |
1378 | 'Because this RenderSliver has sizedByParent set to false, it must set its geometry in performLayout().', |
1379 | ); |
1380 | } |
1381 | |
1382 | final List<DiagnosticsNode> information = <DiagnosticsNode>[ |
1383 | ErrorSummary('RenderSliver geometry setter called incorrectly.'), |
1384 | violation, |
1385 | if (hint != null) hint, |
1386 | contract, |
1387 | describeForError('The RenderSliver in question is'), |
1388 | ]; |
1389 | throw FlutterError.fromParts(information); |
1390 | }()); |
1391 | _geometry = value; |
1392 | } |
1393 | |
1394 | @override |
1395 | Rect get semanticBounds => paintBounds; |
1396 | |
1397 | @override |
1398 | Rect get paintBounds { |
1399 | switch (constraints.axis) { |
1400 | case Axis.horizontal: |
1401 | return Rect.fromLTWH(0.0, 0.0, geometry!.paintExtent, constraints.crossAxisExtent); |
1402 | case Axis.vertical: |
1403 | return Rect.fromLTWH(0.0, 0.0, constraints.crossAxisExtent, geometry!.paintExtent); |
1404 | } |
1405 | } |
1406 | |
1407 | @override |
1408 | void debugResetSize() {} |
1409 | |
1410 | @override |
1411 | void debugAssertDoesMeetConstraints() { |
1412 | assert( |
1413 | geometry!.debugAssertIsValid( |
1414 | informationCollector: |
1415 | () => <DiagnosticsNode>[ |
1416 | describeForError('The RenderSliver that returned the offending geometry was'), |
1417 | ], |
1418 | ), |
1419 | ); |
1420 | assert(() { |
1421 | if (geometry!.paintOrigin + geometry!.paintExtent > constraints.remainingPaintExtent) { |
1422 | throw FlutterError.fromParts(<DiagnosticsNode>[ |
1423 | ErrorSummary( |
1424 | 'SliverGeometry has a paintOffset that exceeds the remainingPaintExtent from the constraints.', |
1425 | ), |
1426 | describeForError( |
1427 | 'The render object whose geometry violates the constraints is the following', |
1428 | ), |
1429 | ..._debugCompareFloats( |
1430 | 'remainingPaintExtent', |
1431 | constraints.remainingPaintExtent, |
1432 | 'paintOrigin + paintExtent', |
1433 | geometry!.paintOrigin + geometry!.paintExtent, |
1434 | ), |
1435 | ErrorDescription( |
1436 | 'The paintOrigin and paintExtent must cause the child sliver to paint ' |
1437 | 'within the viewport, and so cannot exceed the remainingPaintExtent.', |
1438 | ), |
1439 | ]); |
1440 | } |
1441 | return true; |
1442 | }()); |
1443 | } |
1444 | |
1445 | @override |
1446 | void performResize() { |
1447 | assert(false); |
1448 | } |
1449 | |
1450 | /// For a center sliver, the distance before the absolute zero scroll offset |
1451 | /// that this sliver can cover. |
1452 | /// |
1453 | /// For example, if an [AxisDirection.down] viewport with an |
1454 | /// [RenderViewport.anchor] of 0.5 has a single sliver with a height of 100.0 |
1455 | /// and its [centerOffsetAdjustment] returns 50.0, then the sliver will be |
1456 | /// centered in the viewport when the scroll offset is 0.0. |
1457 | /// |
1458 | /// The distance here is in the opposite direction of the |
1459 | /// [RenderViewport.axisDirection], so values will typically be positive. |
1460 | double get centerOffsetAdjustment => 0.0; |
1461 | |
1462 | /// Determines the set of render objects located at the given position. |
1463 | /// |
1464 | /// Returns true if the given point is contained in this render object or one |
1465 | /// of its descendants. Adds any render objects that contain the point to the |
1466 | /// given hit test result. |
1467 | /// |
1468 | /// The caller is responsible for providing the position in the local |
1469 | /// coordinate space of the callee. The callee is responsible for checking |
1470 | /// whether the given position is within its bounds. |
1471 | /// |
1472 | /// Hit testing requires layout to be up-to-date but does not require painting |
1473 | /// to be up-to-date. That means a render object can rely upon [performLayout] |
1474 | /// having been called in [hitTest] but cannot rely upon [paint] having been |
1475 | /// called. For example, a render object might be a child of a [RenderOpacity] |
1476 | /// object, which calls [hitTest] on its children when its opacity is zero |
1477 | /// even though it does not [paint] its children. |
1478 | /// |
1479 | /// ## Coordinates for RenderSliver objects |
1480 | /// |
1481 | /// The `mainAxisPosition` is the distance in the [AxisDirection] (after |
1482 | /// applying the [GrowthDirection]) from the edge of the sliver's painted |
1483 | /// area. This can be an unusual direction, for example in the |
1484 | /// [AxisDirection.up] case this is a distance from the _bottom_ of the |
1485 | /// sliver's painted area. |
1486 | /// |
1487 | /// The `crossAxisPosition` is the distance in the other axis. If the cross |
1488 | /// axis is horizontal (i.e. the [SliverConstraints.axisDirection] is either |
1489 | /// [AxisDirection.down] or [AxisDirection.up]), then the `crossAxisPosition` |
1490 | /// is a distance from the left edge of the sliver. If the cross axis is |
1491 | /// vertical (i.e. the [SliverConstraints.axisDirection] is either |
1492 | /// [AxisDirection.right] or [AxisDirection.left]), then the |
1493 | /// `crossAxisPosition` is a distance from the top edge of the sliver. |
1494 | /// |
1495 | /// ## Implementing hit testing for slivers |
1496 | /// |
1497 | /// The most straight-forward way to implement hit testing for a new sliver |
1498 | /// render object is to override its [hitTestSelf] and [hitTestChildren] |
1499 | /// methods. |
1500 | bool hitTest( |
1501 | SliverHitTestResult result, { |
1502 | required double mainAxisPosition, |
1503 | required double crossAxisPosition, |
1504 | }) { |
1505 | if (mainAxisPosition >= 0.0 && |
1506 | mainAxisPosition < geometry!.hitTestExtent && |
1507 | crossAxisPosition >= 0.0 && |
1508 | crossAxisPosition < constraints.crossAxisExtent) { |
1509 | if (hitTestChildren( |
1510 | result, |
1511 | mainAxisPosition: mainAxisPosition, |
1512 | crossAxisPosition: crossAxisPosition, |
1513 | ) || |
1514 | hitTestSelf(mainAxisPosition: mainAxisPosition, crossAxisPosition: crossAxisPosition)) { |
1515 | result.add( |
1516 | SliverHitTestEntry( |
1517 | this, |
1518 | mainAxisPosition: mainAxisPosition, |
1519 | crossAxisPosition: crossAxisPosition, |
1520 | ), |
1521 | ); |
1522 | return true; |
1523 | } |
1524 | } |
1525 | return false; |
1526 | } |
1527 | |
1528 | /// Override this method if this render object can be hit even if its |
1529 | /// children were not hit. |
1530 | /// |
1531 | /// Used by [hitTest]. If you override [hitTest] and do not call this |
1532 | /// function, then you don't need to implement this function. |
1533 | /// |
1534 | /// For a discussion of the semantics of the arguments, see [hitTest]. |
1535 | @protected |
1536 | bool hitTestSelf({required double mainAxisPosition, required double crossAxisPosition}) => false; |
1537 | |
1538 | /// Override this method to check whether any children are located at the |
1539 | /// given position. |
1540 | /// |
1541 | /// Typically children should be hit-tested in reverse paint order so that |
1542 | /// hit tests at locations where children overlap hit the child that is |
1543 | /// visually "on top" (i.e., paints later). |
1544 | /// |
1545 | /// Used by [hitTest]. If you override [hitTest] and do not call this |
1546 | /// function, then you don't need to implement this function. |
1547 | /// |
1548 | /// For a discussion of the semantics of the arguments, see [hitTest]. |
1549 | @protected |
1550 | bool hitTestChildren( |
1551 | SliverHitTestResult result, { |
1552 | required double mainAxisPosition, |
1553 | required double crossAxisPosition, |
1554 | }) => false; |
1555 | |
1556 | /// Computes the portion of the region from `from` to `to` that is visible, |
1557 | /// assuming that only the region from the [SliverConstraints.scrollOffset] |
1558 | /// that is [SliverConstraints.remainingPaintExtent] high is visible, and that |
1559 | /// the relationship between scroll offsets and paint offsets is linear. |
1560 | /// |
1561 | /// For example, if the constraints have a scroll offset of 100 and a |
1562 | /// remaining paint extent of 100, and the arguments to this method describe |
1563 | /// the region 50..150, then the returned value would be 50 (from scroll |
1564 | /// offset 100 to scroll offset 150). |
1565 | /// |
1566 | /// This method is not useful if there is not a 1:1 relationship between |
1567 | /// consumed scroll offset and consumed paint extent. For example, if the |
1568 | /// sliver always paints the same amount but consumes a scroll offset extent |
1569 | /// that is proportional to the [SliverConstraints.scrollOffset], then this |
1570 | /// function's results will not be consistent. |
1571 | // This could be a static method but isn't, because it would be less convenient |
1572 | // to call it from subclasses if it was. |
1573 | double calculatePaintOffset( |
1574 | SliverConstraints constraints, { |
1575 | required double from, |
1576 | required double to, |
1577 | }) { |
1578 | assert(from <= to); |
1579 | final double a = constraints.scrollOffset; |
1580 | final double b = constraints.scrollOffset + constraints.remainingPaintExtent; |
1581 | // the clamp on the next line is to avoid floating point rounding errors |
1582 | return clampDouble( |
1583 | clampDouble(to, a, b) - clampDouble(from, a, b), |
1584 | 0.0, |
1585 | constraints.remainingPaintExtent, |
1586 | ); |
1587 | } |
1588 | |
1589 | /// Computes the portion of the region from `from` to `to` that is within |
1590 | /// the cache extent of the viewport, assuming that only the region from the |
1591 | /// [SliverConstraints.cacheOrigin] that is |
1592 | /// [SliverConstraints.remainingCacheExtent] high is visible, and that |
1593 | /// the relationship between scroll offsets and paint offsets is linear. |
1594 | /// |
1595 | /// This method is not useful if there is not a 1:1 relationship between |
1596 | /// consumed scroll offset and consumed cache extent. |
1597 | double calculateCacheOffset( |
1598 | SliverConstraints constraints, { |
1599 | required double from, |
1600 | required double to, |
1601 | }) { |
1602 | assert(from <= to); |
1603 | final double a = constraints.scrollOffset + constraints.cacheOrigin; |
1604 | final double b = constraints.scrollOffset + constraints.remainingCacheExtent; |
1605 | // the clamp on the next line is to avoid floating point rounding errors |
1606 | return clampDouble( |
1607 | clampDouble(to, a, b) - clampDouble(from, a, b), |
1608 | 0.0, |
1609 | constraints.remainingCacheExtent, |
1610 | ); |
1611 | } |
1612 | |
1613 | /// Returns the distance from the leading _visible_ edge of the sliver to the |
1614 | /// side of the given child closest to that edge. |
1615 | /// |
1616 | /// For example, if the [constraints] describe this sliver as having an axis |
1617 | /// direction of [AxisDirection.down], then this is the distance from the top |
1618 | /// of the visible portion of the sliver to the top of the child. On the other |
1619 | /// hand, if the [constraints] describe this sliver as having an axis |
1620 | /// direction of [AxisDirection.up], then this is the distance from the bottom |
1621 | /// of the visible portion of the sliver to the bottom of the child. In both |
1622 | /// cases, this is the direction of increasing |
1623 | /// [SliverConstraints.scrollOffset] and |
1624 | /// [SliverLogicalParentData.layoutOffset]. |
1625 | /// |
1626 | /// For children that are [RenderSliver]s, the leading edge of the _child_ |
1627 | /// will be the leading _visible_ edge of the child, not the part of the child |
1628 | /// that would locally be a scroll offset 0.0. For children that are not |
1629 | /// [RenderSliver]s, for example a [RenderBox] child, it's the actual distance |
1630 | /// to the edge of the box, since those boxes do not know how to handle being |
1631 | /// scrolled. |
1632 | /// |
1633 | /// This method differs from [childScrollOffset] in that |
1634 | /// [childMainAxisPosition] gives the distance from the leading _visible_ edge |
1635 | /// of the sliver whereas [childScrollOffset] gives the distance from the |
1636 | /// sliver's zero scroll offset. |
1637 | /// |
1638 | /// Calling this for a child that is not visible is not valid. |
1639 | @protected |
1640 | double childMainAxisPosition(covariant RenderObject child) { |
1641 | assert(() { |
1642 | throw FlutterError( |
1643 | '${objectRuntimeType(this, 'RenderSliver')} does not implement childPosition.', |
1644 | ); |
1645 | }()); |
1646 | return 0.0; |
1647 | } |
1648 | |
1649 | /// Returns the distance along the cross axis from the zero of the cross axis |
1650 | /// in this sliver's [paint] coordinate space to the nearest side of the given |
1651 | /// child. |
1652 | /// |
1653 | /// For example, if the [constraints] describe this sliver as having an axis |
1654 | /// direction of [AxisDirection.down], then this is the distance from the left |
1655 | /// of the sliver to the left of the child. Similarly, if the [constraints] |
1656 | /// describe this sliver as having an axis direction of [AxisDirection.up], |
1657 | /// then this is value is the same. If the axis direction is |
1658 | /// [AxisDirection.left] or [AxisDirection.right], then it is the distance |
1659 | /// from the top of the sliver to the top of the child. |
1660 | /// |
1661 | /// Calling this for a child that is not visible is not valid. |
1662 | @protected |
1663 | double childCrossAxisPosition(covariant RenderObject child) => 0.0; |
1664 | |
1665 | /// Returns the scroll offset for the leading edge of the given child. |
1666 | /// |
1667 | /// The `child` must be a child of this sliver. |
1668 | /// |
1669 | /// This method differs from [childMainAxisPosition] in that |
1670 | /// [childMainAxisPosition] gives the distance from the leading _visible_ edge |
1671 | /// of the sliver whereas [childScrollOffset] gives the distance from sliver's |
1672 | /// zero scroll offset. |
1673 | double? childScrollOffset(covariant RenderObject child) { |
1674 | assert(child.parent == this); |
1675 | return 0.0; |
1676 | } |
1677 | |
1678 | @override |
1679 | void applyPaintTransform(RenderObject child, Matrix4 transform) { |
1680 | assert(() { |
1681 | throw FlutterError( |
1682 | '${objectRuntimeType(this, 'RenderSliver')} does not implement applyPaintTransform.', |
1683 | ); |
1684 | }()); |
1685 | } |
1686 | |
1687 | /// This returns a [Size] with dimensions relative to the leading edge of the |
1688 | /// sliver, specifically the same offset that is given to the [paint] method. |
1689 | /// This means that the dimensions may be negative. |
1690 | /// |
1691 | /// This is only valid after [layout] has completed. |
1692 | /// |
1693 | /// See also: |
1694 | /// |
1695 | /// * [getAbsoluteSize], which returns absolute size. |
1696 | @protected |
1697 | Size getAbsoluteSizeRelativeToOrigin() { |
1698 | assert(geometry != null); |
1699 | assert(!debugNeedsLayout); |
1700 | return switch (applyGrowthDirectionToAxisDirection( |
1701 | constraints.axisDirection, |
1702 | constraints.growthDirection, |
1703 | )) { |
1704 | AxisDirection.up => Size(constraints.crossAxisExtent, -geometry!.paintExtent), |
1705 | AxisDirection.down => Size(constraints.crossAxisExtent, geometry!.paintExtent), |
1706 | AxisDirection.left => Size(-geometry!.paintExtent, constraints.crossAxisExtent), |
1707 | AxisDirection.right => Size(geometry!.paintExtent, constraints.crossAxisExtent), |
1708 | }; |
1709 | } |
1710 | |
1711 | /// This returns the absolute [Size] of the sliver. |
1712 | /// |
1713 | /// The dimensions are always positive and calling this is only valid after |
1714 | /// [layout] has completed. |
1715 | /// |
1716 | /// See also: |
1717 | /// |
1718 | /// * [getAbsoluteSizeRelativeToOrigin], which returns the size relative to |
1719 | /// the leading edge of the sliver. |
1720 | @protected |
1721 | Size getAbsoluteSize() { |
1722 | assert(geometry != null); |
1723 | assert(!debugNeedsLayout); |
1724 | switch (constraints.axisDirection) { |
1725 | case AxisDirection.up: |
1726 | case AxisDirection.down: |
1727 | return Size(constraints.crossAxisExtent, geometry!.paintExtent); |
1728 | case AxisDirection.right: |
1729 | case AxisDirection.left: |
1730 | return Size(geometry!.paintExtent, constraints.crossAxisExtent); |
1731 | } |
1732 | } |
1733 | |
1734 | void _debugDrawArrow( |
1735 | Canvas canvas, |
1736 | Paint paint, |
1737 | Offset p0, |
1738 | Offset p1, |
1739 | GrowthDirection direction, |
1740 | ) { |
1741 | assert(() { |
1742 | if (p0 == p1) { |
1743 | return true; |
1744 | } |
1745 | assert(p0.dx == p1.dx || p0.dy == p1.dy); // must be axis-aligned |
1746 | final double d = (p1 - p0).distance * 0.2; |
1747 | final Offset temp; |
1748 | double dx1, dx2, dy1, dy2; |
1749 | switch (direction) { |
1750 | case GrowthDirection.forward: |
1751 | dx1 = dx2 = dy1 = dy2 = d; |
1752 | case GrowthDirection.reverse: |
1753 | temp = p0; |
1754 | p0 = p1; |
1755 | p1 = temp; |
1756 | dx1 = dx2 = dy1 = dy2 = -d; |
1757 | } |
1758 | if (p0.dx == p1.dx) { |
1759 | dx2 = -dx2; |
1760 | } else { |
1761 | dy2 = -dy2; |
1762 | } |
1763 | canvas.drawPath( |
1764 | Path() |
1765 | ..moveTo(p0.dx, p0.dy) |
1766 | ..lineTo(p1.dx, p1.dy) |
1767 | ..moveTo(p1.dx - dx1, p1.dy - dy1) |
1768 | ..lineTo(p1.dx, p1.dy) |
1769 | ..lineTo(p1.dx - dx2, p1.dy - dy2), |
1770 | paint, |
1771 | ); |
1772 | return true; |
1773 | }()); |
1774 | } |
1775 | |
1776 | @override |
1777 | void debugPaint(PaintingContext context, Offset offset) { |
1778 | assert(() { |
1779 | if (debugPaintSizeEnabled) { |
1780 | final double strokeWidth = math.min(4.0, geometry!.paintExtent / 30.0); |
1781 | final Paint paint = |
1782 | Paint() |
1783 | ..color = const Color(0xFF33CC33) |
1784 | ..strokeWidth = strokeWidth |
1785 | ..style = PaintingStyle.stroke |
1786 | ..maskFilter = MaskFilter.blur(BlurStyle.solid, strokeWidth); |
1787 | final double arrowExtent = geometry!.paintExtent; |
1788 | final double padding = math.max(2.0, strokeWidth); |
1789 | final Canvas canvas = context.canvas; |
1790 | canvas.drawCircle(offset.translate(padding, padding), padding * 0.5, paint); |
1791 | switch (constraints.axis) { |
1792 | case Axis.vertical: |
1793 | canvas.drawLine(offset, offset.translate(constraints.crossAxisExtent, 0.0), paint); |
1794 | _debugDrawArrow( |
1795 | canvas, |
1796 | paint, |
1797 | offset.translate(constraints.crossAxisExtent * 1.0 / 4.0, padding), |
1798 | offset.translate(constraints.crossAxisExtent * 1.0 / 4.0, arrowExtent - padding), |
1799 | constraints.normalizedGrowthDirection, |
1800 | ); |
1801 | _debugDrawArrow( |
1802 | canvas, |
1803 | paint, |
1804 | offset.translate(constraints.crossAxisExtent * 3.0 / 4.0, padding), |
1805 | offset.translate(constraints.crossAxisExtent * 3.0 / 4.0, arrowExtent - padding), |
1806 | constraints.normalizedGrowthDirection, |
1807 | ); |
1808 | case Axis.horizontal: |
1809 | canvas.drawLine(offset, offset.translate(0.0, constraints.crossAxisExtent), paint); |
1810 | _debugDrawArrow( |
1811 | canvas, |
1812 | paint, |
1813 | offset.translate(padding, constraints.crossAxisExtent * 1.0 / 4.0), |
1814 | offset.translate(arrowExtent - padding, constraints.crossAxisExtent * 1.0 / 4.0), |
1815 | constraints.normalizedGrowthDirection, |
1816 | ); |
1817 | _debugDrawArrow( |
1818 | canvas, |
1819 | paint, |
1820 | offset.translate(padding, constraints.crossAxisExtent * 3.0 / 4.0), |
1821 | offset.translate(arrowExtent - padding, constraints.crossAxisExtent * 3.0 / 4.0), |
1822 | constraints.normalizedGrowthDirection, |
1823 | ); |
1824 | } |
1825 | } |
1826 | return true; |
1827 | }()); |
1828 | } |
1829 | |
1830 | // This override exists only to change the type of the second argument. |
1831 | @override |
1832 | void handleEvent(PointerEvent event, SliverHitTestEntry entry) {} |
1833 | |
1834 | @override |
1835 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
1836 | super.debugFillProperties(properties); |
1837 | properties.add(DiagnosticsProperty<SliverGeometry>('geometry', geometry)); |
1838 | } |
1839 | } |
1840 | |
1841 | /// Mixin for [RenderSliver] subclasses that provides some utility functions. |
1842 | mixin RenderSliverHelpers implements RenderSliver { |
1843 | bool _getRightWayUp(SliverConstraints constraints) { |
1844 | final bool reversed = axisDirectionIsReversed(constraints.axisDirection); |
1845 | return switch (constraints.growthDirection) { |
1846 | GrowthDirection.forward => !reversed, |
1847 | GrowthDirection.reverse => reversed, |
1848 | }; |
1849 | } |
1850 | |
1851 | /// Utility function for [hitTestChildren] for use when the children are |
1852 | /// [RenderBox] widgets. |
1853 | /// |
1854 | /// This function takes care of converting the position from the sliver |
1855 | /// coordinate system to the Cartesian coordinate system used by [RenderBox]. |
1856 | /// |
1857 | /// This function relies on [childMainAxisPosition] to determine the position of |
1858 | /// child in question. |
1859 | /// |
1860 | /// Calling this for a child that is not visible is not valid. |
1861 | @protected |
1862 | bool hitTestBoxChild( |
1863 | BoxHitTestResult result, |
1864 | RenderBox child, { |
1865 | required double mainAxisPosition, |
1866 | required double crossAxisPosition, |
1867 | }) { |
1868 | final bool rightWayUp = _getRightWayUp(constraints); |
1869 | double delta = childMainAxisPosition(child); |
1870 | final double crossAxisDelta = childCrossAxisPosition(child); |
1871 | double absolutePosition = mainAxisPosition - delta; |
1872 | final double absoluteCrossAxisPosition = crossAxisPosition - crossAxisDelta; |
1873 | Offset paintOffset, transformedPosition; |
1874 | switch (constraints.axis) { |
1875 | case Axis.horizontal: |
1876 | if (!rightWayUp) { |
1877 | absolutePosition = child.size.width - absolutePosition; |
1878 | delta = geometry!.paintExtent - child.size.width - delta; |
1879 | } |
1880 | paintOffset = Offset(delta, crossAxisDelta); |
1881 | transformedPosition = Offset(absolutePosition, absoluteCrossAxisPosition); |
1882 | case Axis.vertical: |
1883 | if (!rightWayUp) { |
1884 | absolutePosition = child.size.height - absolutePosition; |
1885 | delta = geometry!.paintExtent - child.size.height - delta; |
1886 | } |
1887 | paintOffset = Offset(crossAxisDelta, delta); |
1888 | transformedPosition = Offset(absoluteCrossAxisPosition, absolutePosition); |
1889 | } |
1890 | return result.addWithOutOfBandPosition( |
1891 | paintOffset: paintOffset, |
1892 | hitTest: (BoxHitTestResult result) { |
1893 | return child.hitTest(result, position: transformedPosition); |
1894 | }, |
1895 | ); |
1896 | } |
1897 | |
1898 | /// Utility function for [applyPaintTransform] for use when the children are |
1899 | /// [RenderBox] widgets. |
1900 | /// |
1901 | /// This function turns the value returned by [childMainAxisPosition] and |
1902 | /// [childCrossAxisPosition]for the child in question into a translation that |
1903 | /// it then applies to the given matrix. |
1904 | /// |
1905 | /// Calling this for a child that is not visible is not valid. |
1906 | @protected |
1907 | void applyPaintTransformForBoxChild(RenderBox child, Matrix4 transform) { |
1908 | final bool rightWayUp = _getRightWayUp(constraints); |
1909 | double delta = childMainAxisPosition(child); |
1910 | final double crossAxisDelta = childCrossAxisPosition(child); |
1911 | switch (constraints.axis) { |
1912 | case Axis.horizontal: |
1913 | if (!rightWayUp) { |
1914 | delta = geometry!.paintExtent - child.size.width - delta; |
1915 | } |
1916 | transform.translate(delta, crossAxisDelta); |
1917 | case Axis.vertical: |
1918 | if (!rightWayUp) { |
1919 | delta = geometry!.paintExtent - child.size.height - delta; |
1920 | } |
1921 | transform.translate(crossAxisDelta, delta); |
1922 | } |
1923 | } |
1924 | } |
1925 | |
1926 | // ADAPTER FOR RENDER BOXES INSIDE SLIVERS |
1927 | // Transitions from the RenderSliver world to the RenderBox world. |
1928 | |
1929 | /// An abstract class for [RenderSliver]s that contains a single [RenderBox]. |
1930 | /// |
1931 | /// See also: |
1932 | /// |
1933 | /// * [RenderSliver], which explains more about the Sliver protocol. |
1934 | /// * [RenderBox], which explains more about the Box protocol. |
1935 | /// * [RenderSliverToBoxAdapter], which extends this class to size the child |
1936 | /// according to its preferred size. |
1937 | /// * [RenderSliverFillRemaining], which extends this class to size the child |
1938 | /// to fill the remaining space in the viewport. |
1939 | abstract class RenderSliverSingleBoxAdapter extends RenderSliver |
1940 | with RenderObjectWithChildMixin<RenderBox>, RenderSliverHelpers { |
1941 | /// Creates a [RenderSliver] that wraps a [RenderBox]. |
1942 | RenderSliverSingleBoxAdapter({RenderBox? child}) { |
1943 | this.child = child; |
1944 | } |
1945 | |
1946 | @override |
1947 | void setupParentData(RenderObject child) { |
1948 | if (child.parentData is! SliverPhysicalParentData) { |
1949 | child.parentData = SliverPhysicalParentData(); |
1950 | } |
1951 | } |
1952 | |
1953 | /// Sets the [SliverPhysicalParentData.paintOffset] for the given child |
1954 | /// according to the [SliverConstraints.axisDirection] and |
1955 | /// [SliverConstraints.growthDirection] and the given geometry. |
1956 | @protected |
1957 | void setChildParentData( |
1958 | RenderObject child, |
1959 | SliverConstraints constraints, |
1960 | SliverGeometry geometry, |
1961 | ) { |
1962 | final SliverPhysicalParentData childParentData = child.parentData! as SliverPhysicalParentData; |
1963 | childParentData.paintOffset = switch (applyGrowthDirectionToAxisDirection( |
1964 | constraints.axisDirection, |
1965 | constraints.growthDirection, |
1966 | )) { |
1967 | AxisDirection.up => Offset( |
1968 | 0.0, |
1969 | geometry.paintExtent + constraints.scrollOffset - geometry.scrollExtent, |
1970 | ), |
1971 | AxisDirection.left => Offset( |
1972 | geometry.paintExtent + constraints.scrollOffset - geometry.scrollExtent, |
1973 | 0.0, |
1974 | ), |
1975 | AxisDirection.right => Offset(-constraints.scrollOffset, 0.0), |
1976 | AxisDirection.down => Offset(0.0, -constraints.scrollOffset), |
1977 | }; |
1978 | } |
1979 | |
1980 | @override |
1981 | bool hitTestChildren( |
1982 | SliverHitTestResult result, { |
1983 | required double mainAxisPosition, |
1984 | required double crossAxisPosition, |
1985 | }) { |
1986 | assert(geometry!.hitTestExtent > 0.0); |
1987 | if (child != null) { |
1988 | return hitTestBoxChild( |
1989 | BoxHitTestResult.wrap(result), |
1990 | child!, |
1991 | mainAxisPosition: mainAxisPosition, |
1992 | crossAxisPosition: crossAxisPosition, |
1993 | ); |
1994 | } |
1995 | return false; |
1996 | } |
1997 | |
1998 | @override |
1999 | double childMainAxisPosition(RenderBox child) { |
2000 | return -constraints.scrollOffset; |
2001 | } |
2002 | |
2003 | @override |
2004 | void applyPaintTransform(RenderObject child, Matrix4 transform) { |
2005 | assert(child == this.child); |
2006 | final SliverPhysicalParentData childParentData = child.parentData! as SliverPhysicalParentData; |
2007 | childParentData.applyPaintTransform(transform); |
2008 | } |
2009 | |
2010 | @override |
2011 | void paint(PaintingContext context, Offset offset) { |
2012 | if (child != null && geometry!.visible) { |
2013 | final SliverPhysicalParentData childParentData = |
2014 | child!.parentData! as SliverPhysicalParentData; |
2015 | context.paintChild(child!, offset + childParentData.paintOffset); |
2016 | } |
2017 | } |
2018 | } |
2019 | |
2020 | /// A [RenderSliver] that contains a single [RenderBox]. |
2021 | /// |
2022 | /// The child will not be laid out if it is not visible. It is sized according |
2023 | /// to the child's preferences in the main axis, and with a tight constraint |
2024 | /// forcing it to the dimensions of the viewport in the cross axis. |
2025 | /// |
2026 | /// See also: |
2027 | /// |
2028 | /// * [RenderSliver], which explains more about the Sliver protocol. |
2029 | /// * [RenderBox], which explains more about the Box protocol. |
2030 | /// * [RenderViewport], which allows [RenderSliver] objects to be placed inside |
2031 | /// a [RenderBox] (the opposite of this class). |
2032 | class RenderSliverToBoxAdapter extends RenderSliverSingleBoxAdapter { |
2033 | /// Creates a [RenderSliver] that wraps a [RenderBox]. |
2034 | RenderSliverToBoxAdapter({super.child}); |
2035 | |
2036 | @override |
2037 | void performLayout() { |
2038 | if (child == null) { |
2039 | geometry = SliverGeometry.zero; |
2040 | return; |
2041 | } |
2042 | final SliverConstraints constraints = this.constraints; |
2043 | child!.layout(constraints.asBoxConstraints(), parentUsesSize: true); |
2044 | final double childExtent = switch (constraints.axis) { |
2045 | Axis.horizontal => child!.size.width, |
2046 | Axis.vertical => child!.size.height, |
2047 | }; |
2048 | final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: childExtent); |
2049 | final double cacheExtent = calculateCacheOffset(constraints, from: 0.0, to: childExtent); |
2050 | |
2051 | assert(paintedChildSize.isFinite); |
2052 | assert(paintedChildSize >= 0.0); |
2053 | geometry = SliverGeometry( |
2054 | scrollExtent: childExtent, |
2055 | paintExtent: paintedChildSize, |
2056 | cacheExtent: cacheExtent, |
2057 | maxPaintExtent: childExtent, |
2058 | hitTestExtent: paintedChildSize, |
2059 | hasVisualOverflow: |
2060 | childExtent > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0, |
2061 | ); |
2062 | setChildParentData(child!, constraints, geometry!); |
2063 | } |
2064 | } |
2065 |
Definitions
- SliverLayoutDimensions
- SliverLayoutDimensions
- ==
- toString
- hashCode
- GrowthDirection
- applyGrowthDirectionToAxisDirection
- applyGrowthDirectionToScrollDirection
- SliverConstraints
- SliverConstraints
- copyWith
- axis
- normalizedGrowthDirection
- isTight
- isNormalized
- asBoxConstraints
- debugAssertIsValid
- verify
- verifyDouble
- ==
- hashCode
- toString
- SliverGeometry
- SliverGeometry
- copyWith
- debugAssertIsValid
- verify
- toStringShort
- debugFillProperties
- SliverHitTestResult
- SliverHitTestResult
- wrap
- addWithAxisOffset
- SliverHitTestEntry
- SliverHitTestEntry
- toString
- SliverLogicalParentData
- toString
- SliverLogicalContainerParentData
- SliverPhysicalParentData
- applyPaintTransform
- toString
- SliverPhysicalContainerParentData
- _debugCompareFloats
- RenderSliver
- ensureSemantics
- constraints
- geometry
- geometry
- semanticBounds
- paintBounds
- debugResetSize
- debugAssertDoesMeetConstraints
- performResize
- centerOffsetAdjustment
- hitTest
- hitTestSelf
- hitTestChildren
- calculatePaintOffset
- calculateCacheOffset
- childMainAxisPosition
- childCrossAxisPosition
- childScrollOffset
- applyPaintTransform
- getAbsoluteSizeRelativeToOrigin
- getAbsoluteSize
- _debugDrawArrow
- debugPaint
- handleEvent
- debugFillProperties
- RenderSliverHelpers
- _getRightWayUp
- hitTestBoxChild
- applyPaintTransformForBoxChild
- RenderSliverSingleBoxAdapter
- RenderSliverSingleBoxAdapter
- setupParentData
- setChildParentData
- hitTestChildren
- childMainAxisPosition
- applyPaintTransform
- paint
- RenderSliverToBoxAdapter
- RenderSliverToBoxAdapter
Learn more about Flutter for embedded and desktop on industrialflutter.com