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/widgets.dart'; |
6 | library; |
7 | |
8 | import 'dart:math' as math; |
9 | |
10 | import 'package:vector_math/vector_math_64.dart'; |
11 | |
12 | import 'debug.dart'; |
13 | import 'object.dart'; |
14 | import 'sliver.dart'; |
15 | |
16 | /// Insets a [RenderSliver] by applying [resolvedPadding] on each side. |
17 | /// |
18 | /// A [RenderSliverEdgeInsetsPadding] subclass wraps the [SliverGeometry.layoutExtent] |
19 | /// of its child. Any incoming [SliverConstraints.overlap] is ignored and not |
20 | /// passed on to the child. |
21 | /// |
22 | /// {@template flutter.rendering.RenderSliverEdgeInsetsPadding} |
23 | /// Applying padding in the main extent of the viewport to slivers that have scroll effects is likely to have |
24 | /// undesired effects. For example, wrapping a [SliverPersistentHeader] with |
25 | /// `pinned:true` will cause only the appbar to stay pinned while the padding will scroll away. |
26 | /// {@endtemplate} |
27 | abstract class RenderSliverEdgeInsetsPadding extends RenderSliver |
28 | with RenderObjectWithChildMixin<RenderSliver> { |
29 | /// The amount to pad the child in each dimension. |
30 | /// |
31 | /// The offsets are specified in terms of visual edges, left, top, right, and |
32 | /// bottom. These values are not affected by the [TextDirection]. |
33 | /// |
34 | /// Must not be null or contain negative values when [performLayout] is called. |
35 | EdgeInsets? get resolvedPadding; |
36 | |
37 | /// The padding in the scroll direction on the side nearest the 0.0 scroll direction. |
38 | /// |
39 | /// Only valid after layout has started, since before layout the render object |
40 | /// doesn't know what direction it will be laid out in. |
41 | double get beforePadding { |
42 | assert(resolvedPadding != null); |
43 | return switch (applyGrowthDirectionToAxisDirection( |
44 | constraints.axisDirection, |
45 | constraints.growthDirection, |
46 | )) { |
47 | AxisDirection.up => resolvedPadding!.bottom, |
48 | AxisDirection.right => resolvedPadding!.left, |
49 | AxisDirection.down => resolvedPadding!.top, |
50 | AxisDirection.left => resolvedPadding!.right, |
51 | }; |
52 | } |
53 | |
54 | /// The padding in the scroll direction on the side furthest from the 0.0 scroll offset. |
55 | /// |
56 | /// Only valid after layout has started, since before layout the render object |
57 | /// doesn't know what direction it will be laid out in. |
58 | double get afterPadding { |
59 | assert(resolvedPadding != null); |
60 | return switch (applyGrowthDirectionToAxisDirection( |
61 | constraints.axisDirection, |
62 | constraints.growthDirection, |
63 | )) { |
64 | AxisDirection.up => resolvedPadding!.top, |
65 | AxisDirection.right => resolvedPadding!.right, |
66 | AxisDirection.down => resolvedPadding!.bottom, |
67 | AxisDirection.left => resolvedPadding!.left, |
68 | }; |
69 | } |
70 | |
71 | /// The total padding in the [SliverConstraints.axisDirection]. (In other |
72 | /// words, for a vertical downwards-growing list, the sum of the padding on |
73 | /// the top and bottom.) |
74 | /// |
75 | /// Only valid after layout has started, since before layout the render object |
76 | /// doesn't know what direction it will be laid out in. |
77 | double get mainAxisPadding { |
78 | assert(resolvedPadding != null); |
79 | return resolvedPadding!.along(constraints.axis); |
80 | } |
81 | |
82 | /// The total padding in the cross-axis direction. (In other words, for a |
83 | /// vertical downwards-growing list, the sum of the padding on the left and |
84 | /// right.) |
85 | /// |
86 | /// Only valid after layout has started, since before layout the render object |
87 | /// doesn't know what direction it will be laid out in. |
88 | double get crossAxisPadding { |
89 | assert(resolvedPadding != null); |
90 | return switch (constraints.axis) { |
91 | Axis.horizontal => resolvedPadding!.vertical, |
92 | Axis.vertical => resolvedPadding!.horizontal, |
93 | }; |
94 | } |
95 | |
96 | @override |
97 | void setupParentData(RenderObject child) { |
98 | if (child.parentData is! SliverPhysicalParentData) { |
99 | child.parentData = SliverPhysicalParentData(); |
100 | } |
101 | } |
102 | |
103 | @override |
104 | void performLayout() { |
105 | final SliverConstraints constraints = this.constraints; |
106 | double paintOffset({required double from, required double to}) => |
107 | calculatePaintOffset(constraints, from: from, to: to); |
108 | double cacheOffset({required double from, required double to}) => |
109 | calculateCacheOffset(constraints, from: from, to: to); |
110 | |
111 | assert(this.resolvedPadding != null); |
112 | final EdgeInsets resolvedPadding = this.resolvedPadding!; |
113 | final double beforePadding = this.beforePadding; |
114 | final double afterPadding = this.afterPadding; |
115 | final double mainAxisPadding = this.mainAxisPadding; |
116 | final double crossAxisPadding = this.crossAxisPadding; |
117 | if (child == null) { |
118 | final double paintExtent = paintOffset(from: 0.0, to: mainAxisPadding); |
119 | final double cacheExtent = cacheOffset(from: 0.0, to: mainAxisPadding); |
120 | geometry = SliverGeometry( |
121 | scrollExtent: mainAxisPadding, |
122 | paintExtent: math.min(paintExtent, constraints.remainingPaintExtent), |
123 | maxPaintExtent: mainAxisPadding, |
124 | cacheExtent: cacheExtent, |
125 | ); |
126 | return; |
127 | } |
128 | final double beforePaddingPaintExtent = paintOffset(from: 0.0, to: beforePadding); |
129 | double overlap = constraints.overlap; |
130 | if (overlap > 0) { |
131 | overlap = math.max(0.0, constraints.overlap - beforePaddingPaintExtent); |
132 | } |
133 | child!.layout( |
134 | constraints.copyWith( |
135 | scrollOffset: math.max(0.0, constraints.scrollOffset - beforePadding), |
136 | cacheOrigin: math.min(0.0, constraints.cacheOrigin + beforePadding), |
137 | overlap: overlap, |
138 | remainingPaintExtent: |
139 | constraints.remainingPaintExtent - paintOffset(from: 0.0, to: beforePadding), |
140 | remainingCacheExtent: |
141 | constraints.remainingCacheExtent - cacheOffset(from: 0.0, to: beforePadding), |
142 | crossAxisExtent: math.max(0.0, constraints.crossAxisExtent - crossAxisPadding), |
143 | precedingScrollExtent: beforePadding + constraints.precedingScrollExtent, |
144 | ), |
145 | parentUsesSize: true, |
146 | ); |
147 | final SliverGeometry childLayoutGeometry = child!.geometry!; |
148 | if (childLayoutGeometry.scrollOffsetCorrection != null) { |
149 | geometry = SliverGeometry(scrollOffsetCorrection: childLayoutGeometry.scrollOffsetCorrection); |
150 | return; |
151 | } |
152 | final double scrollExtent = childLayoutGeometry.scrollExtent; |
153 | final double beforePaddingCacheExtent = cacheOffset(from: 0.0, to: beforePadding); |
154 | final double afterPaddingCacheExtent = cacheOffset( |
155 | from: beforePadding + scrollExtent, |
156 | to: mainAxisPadding + scrollExtent, |
157 | ); |
158 | final double afterPaddingPaintExtent = paintOffset( |
159 | from: beforePadding + scrollExtent, |
160 | to: mainAxisPadding + scrollExtent, |
161 | ); |
162 | final double mainAxisPaddingCacheExtent = beforePaddingCacheExtent + afterPaddingCacheExtent; |
163 | final double mainAxisPaddingPaintExtent = beforePaddingPaintExtent + afterPaddingPaintExtent; |
164 | final double paintExtent = math.min( |
165 | beforePaddingPaintExtent + |
166 | math.max( |
167 | childLayoutGeometry.paintExtent, |
168 | childLayoutGeometry.layoutExtent + afterPaddingPaintExtent, |
169 | ), |
170 | constraints.remainingPaintExtent, |
171 | ); |
172 | geometry = SliverGeometry( |
173 | paintOrigin: childLayoutGeometry.paintOrigin, |
174 | scrollExtent: mainAxisPadding + scrollExtent, |
175 | paintExtent: paintExtent, |
176 | layoutExtent: math.min( |
177 | mainAxisPaddingPaintExtent + childLayoutGeometry.layoutExtent, |
178 | paintExtent, |
179 | ), |
180 | cacheExtent: math.min( |
181 | mainAxisPaddingCacheExtent + childLayoutGeometry.cacheExtent, |
182 | constraints.remainingCacheExtent, |
183 | ), |
184 | maxPaintExtent: mainAxisPadding + childLayoutGeometry.maxPaintExtent, |
185 | hitTestExtent: math.max( |
186 | mainAxisPaddingPaintExtent + childLayoutGeometry.paintExtent, |
187 | beforePaddingPaintExtent + childLayoutGeometry.hitTestExtent, |
188 | ), |
189 | hasVisualOverflow: childLayoutGeometry.hasVisualOverflow, |
190 | ); |
191 | final double calculatedOffset = switch (applyGrowthDirectionToAxisDirection( |
192 | constraints.axisDirection, |
193 | constraints.growthDirection, |
194 | )) { |
195 | AxisDirection.up => paintOffset( |
196 | from: resolvedPadding.bottom + scrollExtent, |
197 | to: resolvedPadding.vertical + scrollExtent, |
198 | ), |
199 | AxisDirection.left => paintOffset( |
200 | from: resolvedPadding.right + scrollExtent, |
201 | to: resolvedPadding.horizontal + scrollExtent, |
202 | ), |
203 | AxisDirection.right => paintOffset(from: 0.0, to: resolvedPadding.left), |
204 | AxisDirection.down => paintOffset(from: 0.0, to: resolvedPadding.top), |
205 | }; |
206 | final SliverPhysicalParentData childParentData = child!.parentData! as SliverPhysicalParentData; |
207 | childParentData.paintOffset = switch (constraints.axis) { |
208 | Axis.horizontal => Offset(calculatedOffset, resolvedPadding.top), |
209 | Axis.vertical => Offset(resolvedPadding.left, calculatedOffset), |
210 | }; |
211 | assert(beforePadding == this.beforePadding); |
212 | assert(afterPadding == this.afterPadding); |
213 | assert(mainAxisPadding == this.mainAxisPadding); |
214 | assert(crossAxisPadding == this.crossAxisPadding); |
215 | } |
216 | |
217 | @override |
218 | bool hitTestChildren( |
219 | SliverHitTestResult result, { |
220 | required double mainAxisPosition, |
221 | required double crossAxisPosition, |
222 | }) { |
223 | if (child != null && child!.geometry!.hitTestExtent > 0.0) { |
224 | final SliverPhysicalParentData childParentData = |
225 | child!.parentData! as SliverPhysicalParentData; |
226 | return result.addWithAxisOffset( |
227 | mainAxisPosition: mainAxisPosition, |
228 | crossAxisPosition: crossAxisPosition, |
229 | mainAxisOffset: childMainAxisPosition(child!), |
230 | crossAxisOffset: childCrossAxisPosition(child!), |
231 | paintOffset: childParentData.paintOffset, |
232 | hitTest: child!.hitTest, |
233 | ); |
234 | } |
235 | return false; |
236 | } |
237 | |
238 | @override |
239 | double childMainAxisPosition(RenderSliver child) { |
240 | assert(child == this.child); |
241 | return calculatePaintOffset(constraints, from: 0.0, to: beforePadding); |
242 | } |
243 | |
244 | @override |
245 | double childCrossAxisPosition(RenderSliver child) { |
246 | assert(child == this.child); |
247 | assert(resolvedPadding != null); |
248 | return switch (constraints.axis) { |
249 | Axis.horizontal => resolvedPadding!.top, |
250 | Axis.vertical => resolvedPadding!.left, |
251 | }; |
252 | } |
253 | |
254 | @override |
255 | double? childScrollOffset(RenderObject child) { |
256 | assert(child.parent == this); |
257 | return beforePadding; |
258 | } |
259 | |
260 | @override |
261 | void applyPaintTransform(RenderObject child, Matrix4 transform) { |
262 | assert(child == this.child); |
263 | final SliverPhysicalParentData childParentData = child.parentData! as SliverPhysicalParentData; |
264 | childParentData.applyPaintTransform(transform); |
265 | } |
266 | |
267 | @override |
268 | void paint(PaintingContext context, Offset offset) { |
269 | if (child != null && child!.geometry!.visible) { |
270 | final SliverPhysicalParentData childParentData = |
271 | child!.parentData! as SliverPhysicalParentData; |
272 | context.paintChild(child!, offset + childParentData.paintOffset); |
273 | } |
274 | } |
275 | |
276 | @override |
277 | void debugPaint(PaintingContext context, Offset offset) { |
278 | super.debugPaint(context, offset); |
279 | assert(() { |
280 | if (debugPaintSizeEnabled) { |
281 | final Size parentSize = getAbsoluteSize(); |
282 | final Rect outerRect = offset & parentSize; |
283 | Rect? innerRect; |
284 | if (child != null) { |
285 | final Size childSize = child!.getAbsoluteSize(); |
286 | final SliverPhysicalParentData childParentData = |
287 | child!.parentData! as SliverPhysicalParentData; |
288 | innerRect = (offset + childParentData.paintOffset) & childSize; |
289 | assert(innerRect.top >= outerRect.top); |
290 | assert(innerRect.left >= outerRect.left); |
291 | assert(innerRect.right <= outerRect.right); |
292 | assert(innerRect.bottom <= outerRect.bottom); |
293 | } |
294 | debugPaintPadding(context.canvas, outerRect, innerRect); |
295 | } |
296 | return true; |
297 | }()); |
298 | } |
299 | } |
300 | |
301 | /// Insets a [RenderSliver], applying padding on each side. |
302 | /// |
303 | /// A [RenderSliverPadding] object wraps the [SliverGeometry.layoutExtent] of |
304 | /// its child. Any incoming [SliverConstraints.overlap] is ignored and not |
305 | /// passed on to the child. |
306 | /// |
307 | /// {@macro flutter.rendering.RenderSliverEdgeInsetsPadding} |
308 | class RenderSliverPadding extends RenderSliverEdgeInsetsPadding { |
309 | /// Creates a render object that insets its child in a viewport. |
310 | /// |
311 | /// The [padding] argument must have non-negative insets. |
312 | RenderSliverPadding({ |
313 | required EdgeInsetsGeometry padding, |
314 | TextDirection? textDirection, |
315 | RenderSliver? child, |
316 | }) : assert(padding.isNonNegative), |
317 | _padding = padding, |
318 | _textDirection = textDirection { |
319 | this.child = child; |
320 | } |
321 | |
322 | @override |
323 | EdgeInsets? get resolvedPadding => _resolvedPadding; |
324 | EdgeInsets? _resolvedPadding; |
325 | |
326 | void _resolve() { |
327 | if (resolvedPadding != null) { |
328 | return; |
329 | } |
330 | _resolvedPadding = padding.resolve(textDirection); |
331 | assert(resolvedPadding!.isNonNegative); |
332 | } |
333 | |
334 | void _markNeedsResolution() { |
335 | _resolvedPadding = null; |
336 | markNeedsLayout(); |
337 | } |
338 | |
339 | /// The amount to pad the child in each dimension. |
340 | /// |
341 | /// If this is set to an [EdgeInsetsDirectional] object, then [textDirection] |
342 | /// must not be null. |
343 | EdgeInsetsGeometry get padding => _padding; |
344 | EdgeInsetsGeometry _padding; |
345 | set padding(EdgeInsetsGeometry value) { |
346 | assert(padding.isNonNegative); |
347 | if (_padding == value) { |
348 | return; |
349 | } |
350 | _padding = value; |
351 | _markNeedsResolution(); |
352 | } |
353 | |
354 | /// The text direction with which to resolve [padding]. |
355 | /// |
356 | /// This may be changed to null, but only after the [padding] has been changed |
357 | /// to a value that does not depend on the direction. |
358 | TextDirection? get textDirection => _textDirection; |
359 | TextDirection? _textDirection; |
360 | set textDirection(TextDirection? value) { |
361 | if (_textDirection == value) { |
362 | return; |
363 | } |
364 | _textDirection = value; |
365 | _markNeedsResolution(); |
366 | } |
367 | |
368 | @override |
369 | void performLayout() { |
370 | _resolve(); |
371 | super.performLayout(); |
372 | } |
373 | |
374 | @override |
375 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
376 | super.debugFillProperties(properties); |
377 | properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding)); |
378 | properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null)); |
379 | } |
380 | } |
381 |
Definitions
- RenderSliverEdgeInsetsPadding
- resolvedPadding
- beforePadding
- afterPadding
- mainAxisPadding
- crossAxisPadding
- setupParentData
- performLayout
- paintOffset
- cacheOffset
- hitTestChildren
- childMainAxisPosition
- childCrossAxisPosition
- childScrollOffset
- applyPaintTransform
- paint
- debugPaint
- RenderSliverPadding
- RenderSliverPadding
- resolvedPadding
- _resolve
- _markNeedsResolution
- padding
- padding
- textDirection
- textDirection
- performLayout
Learn more about Flutter for embedded and desktop on industrialflutter.com