1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
---|---|
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | import 'dart:math'; |
6 | import 'dart:ui' as ui show Color; |
7 | |
8 | import 'package:flutter/animation.dart'; |
9 | import 'package:flutter/foundation.dart'; |
10 | import 'package:flutter/semantics.dart'; |
11 | |
12 | import 'layer.dart'; |
13 | import 'object.dart'; |
14 | import 'proxy_box.dart'; |
15 | import 'sliver.dart'; |
16 | |
17 | /// A base class for sliver render objects that resemble their children. |
18 | /// |
19 | /// A proxy sliver has a single child and mimics all the properties of |
20 | /// that child by calling through to the child for each function in the render |
21 | /// sliver protocol. For example, a proxy sliver determines its geometry by |
22 | /// asking its sliver child to layout with the same constraints and then |
23 | /// matching the geometry. |
24 | /// |
25 | /// A proxy sliver isn't useful on its own because you might as well just |
26 | /// replace the proxy sliver with its child. However, RenderProxySliver is a |
27 | /// useful base class for render objects that wish to mimic most, but not all, |
28 | /// of the properties of their sliver child. |
29 | /// |
30 | /// See also: |
31 | /// |
32 | /// * [RenderProxyBox], a base class for render boxes that resemble their |
33 | /// children. |
34 | abstract class RenderProxySliver extends RenderSliver |
35 | with RenderObjectWithChildMixin<RenderSliver> { |
36 | /// Creates a proxy render sliver. |
37 | /// |
38 | /// Proxy render slivers aren't created directly because they proxy |
39 | /// the render sliver protocol to their sliver [child]. Instead, use one of |
40 | /// the subclasses. |
41 | RenderProxySliver([RenderSliver? child]) { |
42 | this.child = child; |
43 | } |
44 | |
45 | @override |
46 | Rect get semanticBounds { |
47 | if (child != null) { |
48 | return child!.semanticBounds; |
49 | } |
50 | return super.semanticBounds; |
51 | } |
52 | |
53 | @override |
54 | void setupParentData(RenderObject child) { |
55 | if (child.parentData is! SliverPhysicalParentData) { |
56 | child.parentData = SliverPhysicalParentData(); |
57 | } |
58 | } |
59 | |
60 | @override |
61 | void performLayout() { |
62 | assert(child != null); |
63 | child!.layout(constraints, parentUsesSize: true); |
64 | geometry = child!.geometry; |
65 | } |
66 | |
67 | @override |
68 | void paint(PaintingContext context, Offset offset) { |
69 | if (child != null) { |
70 | context.paintChild(child!, offset); |
71 | } |
72 | } |
73 | |
74 | @override |
75 | bool hitTestChildren( |
76 | SliverHitTestResult result, { |
77 | required double mainAxisPosition, |
78 | required double crossAxisPosition, |
79 | }) { |
80 | return child != null && |
81 | child!.geometry!.hitTestExtent > 0 && |
82 | child!.hitTest( |
83 | result, |
84 | mainAxisPosition: mainAxisPosition, |
85 | crossAxisPosition: crossAxisPosition, |
86 | ); |
87 | } |
88 | |
89 | @override |
90 | double childMainAxisPosition(RenderSliver child) { |
91 | assert(child == this.child); |
92 | return 0.0; |
93 | } |
94 | |
95 | @override |
96 | void applyPaintTransform(RenderObject child, Matrix4 transform) { |
97 | final SliverPhysicalParentData childParentData = child.parentData! as SliverPhysicalParentData; |
98 | childParentData.applyPaintTransform(transform); |
99 | } |
100 | } |
101 | |
102 | /// Makes its sliver child partially transparent. |
103 | /// |
104 | /// This class paints its sliver child into an intermediate buffer and then |
105 | /// blends the sliver child back into the scene, partially transparent. |
106 | /// |
107 | /// For values of opacity other than 0.0 and 1.0, this class is relatively |
108 | /// expensive, because it requires painting the sliver child into an intermediate |
109 | /// buffer. For the value 0.0, the sliver child is not painted at all. |
110 | /// For the value 1.0, the sliver child is painted immediately without an |
111 | /// intermediate buffer. |
112 | class RenderSliverOpacity extends RenderProxySliver { |
113 | /// Creates a partially transparent render object. |
114 | /// |
115 | /// The [opacity] argument must be between 0.0 and 1.0, inclusive. |
116 | RenderSliverOpacity({ |
117 | double opacity = 1.0, |
118 | bool alwaysIncludeSemantics = false, |
119 | RenderSliver? sliver, |
120 | }) : assert(opacity >= 0.0 && opacity <= 1.0), |
121 | _opacity = opacity, |
122 | _alwaysIncludeSemantics = alwaysIncludeSemantics, |
123 | _alpha = ui.Color.getAlphaFromOpacity(opacity) { |
124 | child = sliver; |
125 | } |
126 | |
127 | @override |
128 | bool get alwaysNeedsCompositing => child != null && (_alpha > 0); |
129 | |
130 | int _alpha; |
131 | |
132 | /// The fraction to scale the child's alpha value. |
133 | /// |
134 | /// An opacity of one is fully opaque. An opacity of zero is fully transparent |
135 | /// (i.e. invisible). |
136 | /// |
137 | /// Values one and zero are painted with a fast path. Other values require |
138 | /// painting the child into an intermediate buffer, which is expensive. |
139 | double get opacity => _opacity; |
140 | double _opacity; |
141 | set opacity(double value) { |
142 | assert(value >= 0.0 && value <= 1.0); |
143 | if (_opacity == value) { |
144 | return; |
145 | } |
146 | final bool didNeedCompositing = alwaysNeedsCompositing; |
147 | final bool wasVisible = _alpha != 0; |
148 | _opacity = value; |
149 | _alpha = ui.Color.getAlphaFromOpacity(_opacity); |
150 | if (didNeedCompositing != alwaysNeedsCompositing) { |
151 | markNeedsCompositingBitsUpdate(); |
152 | } |
153 | markNeedsPaint(); |
154 | if (wasVisible != (_alpha != 0) && !alwaysIncludeSemantics) { |
155 | markNeedsSemanticsUpdate(); |
156 | } |
157 | } |
158 | |
159 | /// Whether child semantics are included regardless of the opacity. |
160 | /// |
161 | /// If false, semantics are excluded when [opacity] is 0.0. |
162 | /// |
163 | /// Defaults to false. |
164 | bool get alwaysIncludeSemantics => _alwaysIncludeSemantics; |
165 | bool _alwaysIncludeSemantics; |
166 | set alwaysIncludeSemantics(bool value) { |
167 | if (value == _alwaysIncludeSemantics) { |
168 | return; |
169 | } |
170 | _alwaysIncludeSemantics = value; |
171 | markNeedsSemanticsUpdate(); |
172 | } |
173 | |
174 | @override |
175 | void paint(PaintingContext context, Offset offset) { |
176 | if (child != null && child!.geometry!.visible) { |
177 | if (_alpha == 0) { |
178 | // No need to keep the layer. We'll create a new one if necessary. |
179 | layer = null; |
180 | return; |
181 | } |
182 | assert(needsCompositing); |
183 | layer = context.pushOpacity(offset, _alpha, super.paint, oldLayer: layer as OpacityLayer?); |
184 | assert(() { |
185 | layer!.debugCreator = debugCreator; |
186 | return true; |
187 | }()); |
188 | } |
189 | } |
190 | |
191 | @override |
192 | void visitChildrenForSemantics(RenderObjectVisitor visitor) { |
193 | if (child != null && (_alpha != 0 || alwaysIncludeSemantics)) { |
194 | visitor(child!); |
195 | } |
196 | } |
197 | |
198 | @override |
199 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
200 | super.debugFillProperties(properties); |
201 | properties.add(DoubleProperty('opacity', opacity)); |
202 | properties.add( |
203 | FlagProperty( |
204 | 'alwaysIncludeSemantics', |
205 | value: alwaysIncludeSemantics, |
206 | ifTrue: 'alwaysIncludeSemantics', |
207 | ), |
208 | ); |
209 | } |
210 | } |
211 | |
212 | /// A render object that is invisible during hit testing. |
213 | /// |
214 | /// When [ignoring] is true, this render object (and its subtree) is invisible |
215 | /// to hit testing. It still consumes space during layout and paints its sliver |
216 | /// child as usual. It just cannot be the target of located events, because its |
217 | /// render object returns false from [hitTest]. |
218 | /// |
219 | /// ## Semantics |
220 | /// |
221 | /// Using this class may also affect how the semantics subtree underneath is |
222 | /// collected. |
223 | /// |
224 | /// {@macro flutter.widgets.IgnorePointer.semantics} |
225 | /// |
226 | /// {@macro flutter.widgets.IgnorePointer.ignoringSemantics} |
227 | class RenderSliverIgnorePointer extends RenderProxySliver { |
228 | /// Creates a render object that is invisible to hit testing. |
229 | RenderSliverIgnorePointer({ |
230 | RenderSliver? sliver, |
231 | bool ignoring = true, |
232 | @Deprecated( |
233 | 'Create a custom sliver ignore pointer widget instead. ' |
234 | 'This feature was deprecated after v3.8.0-12.0.pre.', |
235 | ) |
236 | bool? ignoringSemantics, |
237 | }) : _ignoring = ignoring, |
238 | _ignoringSemantics = ignoringSemantics { |
239 | child = sliver; |
240 | } |
241 | |
242 | /// Whether this render object is ignored during hit testing. |
243 | /// |
244 | /// Regardless of whether this render object is ignored during hit testing, it |
245 | /// will still consume space during layout and be visible during painting. |
246 | /// |
247 | /// {@macro flutter.widgets.IgnorePointer.semantics} |
248 | bool get ignoring => _ignoring; |
249 | bool _ignoring; |
250 | set ignoring(bool value) { |
251 | if (value == _ignoring) { |
252 | return; |
253 | } |
254 | _ignoring = value; |
255 | if (ignoringSemantics == null) { |
256 | markNeedsSemanticsUpdate(); |
257 | } |
258 | } |
259 | |
260 | /// Whether the semantics of this render object is ignored when compiling the |
261 | /// semantics tree. |
262 | /// |
263 | /// {@macro flutter.widgets.IgnorePointer.ignoringSemantics} |
264 | @Deprecated( |
265 | 'Create a custom sliver ignore pointer widget instead. ' |
266 | 'This feature was deprecated after v3.8.0-12.0.pre.', |
267 | ) |
268 | bool? get ignoringSemantics => _ignoringSemantics; |
269 | bool? _ignoringSemantics; |
270 | set ignoringSemantics(bool? value) { |
271 | if (value == _ignoringSemantics) { |
272 | return; |
273 | } |
274 | _ignoringSemantics = value; |
275 | markNeedsSemanticsUpdate(); |
276 | } |
277 | |
278 | @override |
279 | bool hitTest( |
280 | SliverHitTestResult result, { |
281 | required double mainAxisPosition, |
282 | required double crossAxisPosition, |
283 | }) { |
284 | return !ignoring && |
285 | super.hitTest( |
286 | result, |
287 | mainAxisPosition: mainAxisPosition, |
288 | crossAxisPosition: crossAxisPosition, |
289 | ); |
290 | } |
291 | |
292 | @override |
293 | void visitChildrenForSemantics(RenderObjectVisitor visitor) { |
294 | if (_ignoringSemantics ?? false) { |
295 | return; |
296 | } |
297 | super.visitChildrenForSemantics(visitor); |
298 | } |
299 | |
300 | @override |
301 | void describeSemanticsConfiguration(SemanticsConfiguration config) { |
302 | super.describeSemanticsConfiguration(config); |
303 | // Do not block user interactions if _ignoringSemantics is false; otherwise, |
304 | // delegate to absorbing |
305 | config.isBlockingUserActions = ignoring && (_ignoringSemantics ?? true); |
306 | } |
307 | |
308 | @override |
309 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
310 | super.debugFillProperties(properties); |
311 | properties.add(DiagnosticsProperty<bool>('ignoring', ignoring)); |
312 | properties.add( |
313 | DiagnosticsProperty<bool>( |
314 | 'ignoringSemantics', |
315 | ignoringSemantics, |
316 | description: ignoringSemantics == null ? null : 'implicitly$ignoringSemantics ', |
317 | ), |
318 | ); |
319 | } |
320 | } |
321 | |
322 | /// Lays the sliver child out as if it was in the tree, but without painting |
323 | /// anything, without making the sliver child available for hit testing, and |
324 | /// without taking any room in the parent. |
325 | class RenderSliverOffstage extends RenderProxySliver { |
326 | /// Creates an offstage render object. |
327 | RenderSliverOffstage({bool offstage = true, RenderSliver? sliver}) : _offstage = offstage { |
328 | child = sliver; |
329 | } |
330 | |
331 | /// Whether the sliver child is hidden from the rest of the tree. |
332 | /// |
333 | /// If true, the sliver child is laid out as if it was in the tree, but |
334 | /// without painting anything, without making the sliver child available for |
335 | /// hit testing, and without taking any room in the parent. |
336 | /// |
337 | /// If false, the sliver child is included in the tree as normal. |
338 | bool get offstage => _offstage; |
339 | bool _offstage; |
340 | |
341 | set offstage(bool value) { |
342 | if (value == _offstage) { |
343 | return; |
344 | } |
345 | _offstage = value; |
346 | markNeedsLayoutForSizedByParentChange(); |
347 | } |
348 | |
349 | @override |
350 | void performLayout() { |
351 | assert(child != null); |
352 | child!.layout(constraints, parentUsesSize: true); |
353 | if (!offstage) { |
354 | geometry = child!.geometry; |
355 | } else { |
356 | geometry = SliverGeometry.zero; |
357 | } |
358 | } |
359 | |
360 | @override |
361 | bool hitTest( |
362 | SliverHitTestResult result, { |
363 | required double mainAxisPosition, |
364 | required double crossAxisPosition, |
365 | }) { |
366 | return !offstage && |
367 | super.hitTest( |
368 | result, |
369 | mainAxisPosition: mainAxisPosition, |
370 | crossAxisPosition: crossAxisPosition, |
371 | ); |
372 | } |
373 | |
374 | @override |
375 | bool hitTestChildren( |
376 | SliverHitTestResult result, { |
377 | required double mainAxisPosition, |
378 | required double crossAxisPosition, |
379 | }) { |
380 | return !offstage && |
381 | child != null && |
382 | child!.geometry!.hitTestExtent > 0 && |
383 | child!.hitTest( |
384 | result, |
385 | mainAxisPosition: mainAxisPosition, |
386 | crossAxisPosition: crossAxisPosition, |
387 | ); |
388 | } |
389 | |
390 | @override |
391 | void paint(PaintingContext context, Offset offset) { |
392 | if (offstage) { |
393 | return; |
394 | } |
395 | context.paintChild(child!, offset); |
396 | } |
397 | |
398 | @override |
399 | void visitChildrenForSemantics(RenderObjectVisitor visitor) { |
400 | if (offstage) { |
401 | return; |
402 | } |
403 | super.visitChildrenForSemantics(visitor); |
404 | } |
405 | |
406 | @override |
407 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
408 | super.debugFillProperties(properties); |
409 | properties.add(DiagnosticsProperty<bool>('offstage', offstage)); |
410 | } |
411 | |
412 | @override |
413 | List<DiagnosticsNode> debugDescribeChildren() { |
414 | if (child == null) { |
415 | return <DiagnosticsNode>[]; |
416 | } |
417 | return <DiagnosticsNode>[ |
418 | child!.toDiagnosticsNode( |
419 | name: 'child', |
420 | style: offstage ? DiagnosticsTreeStyle.offstage : DiagnosticsTreeStyle.sparse, |
421 | ), |
422 | ]; |
423 | } |
424 | } |
425 | |
426 | /// Makes its sliver child partially transparent, driven from an [Animation]. |
427 | /// |
428 | /// This is a variant of [RenderSliverOpacity] that uses an [Animation<double>] |
429 | /// rather than a [double] to control the opacity. |
430 | class RenderSliverAnimatedOpacity extends RenderProxySliver |
431 | with RenderAnimatedOpacityMixin<RenderSliver> { |
432 | /// Creates a partially transparent render object. |
433 | RenderSliverAnimatedOpacity({ |
434 | required Animation<double> opacity, |
435 | bool alwaysIncludeSemantics = false, |
436 | RenderSliver? sliver, |
437 | }) { |
438 | this.opacity = opacity; |
439 | this.alwaysIncludeSemantics = alwaysIncludeSemantics; |
440 | child = sliver; |
441 | } |
442 | } |
443 | |
444 | /// Applies a cross-axis constraint to its sliver child. |
445 | /// |
446 | /// This render object takes a [maxExtent] parameter and uses the smaller of |
447 | /// [maxExtent] and the parent's [SliverConstraints.crossAxisExtent] as the |
448 | /// cross axis extent of the [SliverConstraints] passed to the sliver child. |
449 | class RenderSliverConstrainedCrossAxis extends RenderProxySliver { |
450 | /// Creates a render object that constrains the cross axis extent of its sliver child. |
451 | /// |
452 | /// The [maxExtent] parameter must be nonnegative. |
453 | RenderSliverConstrainedCrossAxis({required double maxExtent}) |
454 | : _maxExtent = maxExtent, |
455 | assert(maxExtent >= 0.0); |
456 | |
457 | /// The cross axis extent to apply to the sliver child. |
458 | /// |
459 | /// This value must be nonnegative. |
460 | double get maxExtent => _maxExtent; |
461 | double _maxExtent; |
462 | set maxExtent(double value) { |
463 | if (_maxExtent == value) { |
464 | return; |
465 | } |
466 | _maxExtent = value; |
467 | markNeedsLayout(); |
468 | } |
469 | |
470 | @override |
471 | void performLayout() { |
472 | assert(child != null); |
473 | assert(maxExtent >= 0.0); |
474 | child!.layout( |
475 | constraints.copyWith(crossAxisExtent: min(_maxExtent, constraints.crossAxisExtent)), |
476 | parentUsesSize: true, |
477 | ); |
478 | final SliverGeometry childLayoutGeometry = child!.geometry!; |
479 | geometry = childLayoutGeometry.copyWith( |
480 | crossAxisExtent: min(_maxExtent, constraints.crossAxisExtent), |
481 | ); |
482 | } |
483 | } |
484 |
Definitions
- RenderProxySliver
- RenderProxySliver
- semanticBounds
- setupParentData
- performLayout
- paint
- hitTestChildren
- childMainAxisPosition
- applyPaintTransform
- RenderSliverOpacity
- RenderSliverOpacity
- alwaysNeedsCompositing
- opacity
- opacity
- alwaysIncludeSemantics
- alwaysIncludeSemantics
- paint
- visitChildrenForSemantics
- debugFillProperties
- RenderSliverIgnorePointer
- RenderSliverIgnorePointer
- ignoring
- ignoring
- ignoringSemantics
- ignoringSemantics
- hitTest
- visitChildrenForSemantics
- describeSemanticsConfiguration
- debugFillProperties
- RenderSliverOffstage
- RenderSliverOffstage
- offstage
- offstage
- performLayout
- hitTest
- hitTestChildren
- paint
- visitChildrenForSemantics
- debugFillProperties
- debugDescribeChildren
- RenderSliverAnimatedOpacity
- RenderSliverAnimatedOpacity
- RenderSliverConstrainedCrossAxis
- RenderSliverConstrainedCrossAxis
- maxExtent
- maxExtent
Learn more about Flutter for embedded and desktop on industrialflutter.com