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