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 'animated_cross_fade.dart'; |
6 | /// @docImport 'animated_switcher.dart'; |
7 | /// @docImport 'implicit_animations.dart'; |
8 | /// @docImport 'navigator.dart'; |
9 | /// @docImport 'transitions.dart'; |
10 | library; |
11 | |
12 | import 'package:flutter/rendering.dart'; |
13 | |
14 | import 'basic.dart'; |
15 | import 'framework.dart'; |
16 | import 'sliver.dart'; |
17 | import 'ticker_provider.dart'; |
18 | |
19 | /// Whether to show or hide a child. |
20 | /// |
21 | /// By default, the [visible] property controls whether the [child] is included |
22 | /// in the subtree or not; when it is not [visible], the [replacement] child |
23 | /// (typically a zero-sized box) is included instead. |
24 | /// |
25 | /// A variety of flags can be used to tweak exactly how the child is hidden. |
26 | /// (Changing the flags dynamically is discouraged, as it can cause the [child] |
27 | /// subtree to be rebuilt, with any state in the subtree being discarded. |
28 | /// Typically, only the [visible] flag is changed dynamically.) |
29 | /// |
30 | /// These widgets provide some of the facets of this one: |
31 | /// |
32 | /// * [Opacity], which can stop its child from being painted. |
33 | /// * [Offstage], which can stop its child from being laid out or painted. |
34 | /// * [TickerMode], which can stop its child from being animated. |
35 | /// * [ExcludeSemantics], which can hide the child from accessibility tools. |
36 | /// * [IgnorePointer], which can disable touch interactions with the child. |
37 | /// |
38 | /// Using this widget is not necessary to hide children. The simplest way to |
39 | /// hide a child is just to not include it, or, if a child _must_ be given (e.g. |
40 | /// because the parent is a [StatelessWidget]) then to use [SizedBox.shrink] |
41 | /// instead of the child that would otherwise be included. |
42 | /// |
43 | /// See also: |
44 | /// |
45 | /// * [AnimatedSwitcher], which can fade from one child to the next as the |
46 | /// subtree changes. |
47 | /// * [AnimatedCrossFade], which can fade between two specific children. |
48 | /// * [SliverVisibility], the sliver equivalent of this widget. |
49 | class Visibility extends StatelessWidget { |
50 | /// Control whether the given [child] is [visible]. |
51 | /// |
52 | /// The [maintainSemantics] and [maintainInteractivity] arguments can only be |
53 | /// set if [maintainSize] is set. |
54 | /// |
55 | /// The [maintainSize] argument can only be set if [maintainAnimation] is set. |
56 | /// |
57 | /// The [maintainAnimation] argument can only be set if [maintainState] is |
58 | /// set. |
59 | const Visibility({ |
60 | super.key, |
61 | required this.child, |
62 | this.replacement = const SizedBox.shrink(), |
63 | this.visible = true, |
64 | this.maintainState = false, |
65 | this.maintainAnimation = false, |
66 | this.maintainSize = false, |
67 | this.maintainSemantics = false, |
68 | this.maintainInteractivity = false, |
69 | }) : assert( |
70 | maintainState || !maintainAnimation, |
71 | 'Cannot maintain animations if the state is not also maintained.' , |
72 | ), |
73 | assert( |
74 | maintainAnimation || !maintainSize, |
75 | 'Cannot maintain size if animations are not maintained.' , |
76 | ), |
77 | assert( |
78 | maintainSize || !maintainSemantics, |
79 | 'Cannot maintain semantics if size is not maintained.' , |
80 | ), |
81 | assert( |
82 | maintainSize || !maintainInteractivity, |
83 | 'Cannot maintain interactivity if size is not maintained.' , |
84 | ); |
85 | |
86 | /// Control whether the given [child] is [visible]. |
87 | /// |
88 | /// This is equivalent to the default [Visibility] constructor with all |
89 | /// "maintain" fields set to true. This constructor should be used in place of |
90 | /// an [Opacity] widget that only takes on values of `0.0` or `1.0`, as it |
91 | /// avoids extra compositing when fully opaque. |
92 | const Visibility.maintain({ |
93 | super.key, |
94 | required this.child, |
95 | this.visible = true, |
96 | }) : maintainState = true, |
97 | maintainAnimation = true, |
98 | maintainSize = true, |
99 | maintainSemantics = true, |
100 | maintainInteractivity = true, |
101 | replacement = const SizedBox.shrink(); // Unused since maintainState is always true. |
102 | |
103 | /// The widget to show or hide, as controlled by [visible]. |
104 | /// |
105 | /// {@macro flutter.widgets.ProxyWidget.child} |
106 | final Widget child; |
107 | |
108 | /// The widget to use when the child is not [visible], assuming that none of |
109 | /// the `maintain` flags (in particular, [maintainState]) are set. |
110 | /// |
111 | /// The normal behavior is to replace the widget with a zero by zero box |
112 | /// ([SizedBox.shrink]). |
113 | /// |
114 | /// See also: |
115 | /// |
116 | /// * [AnimatedCrossFade], which can animate between two children. |
117 | final Widget replacement; |
118 | |
119 | /// Switches between showing the [child] or hiding it. |
120 | /// |
121 | /// The `maintain` flags should be set to the same values regardless of the |
122 | /// state of the [visible] property, otherwise they will not operate correctly |
123 | /// (specifically, the state will be lost regardless of the state of |
124 | /// [maintainState] whenever any of the `maintain` flags are changed, since |
125 | /// doing so will result in a subtree shape change). |
126 | /// |
127 | /// Unless [maintainState] is set, the [child] subtree will be disposed |
128 | /// (removed from the tree) while hidden. |
129 | final bool visible; |
130 | |
131 | /// Whether to maintain the [State] objects of the [child] subtree when it is |
132 | /// not [visible]. |
133 | /// |
134 | /// Keeping the state of the subtree is potentially expensive (because it |
135 | /// means all the objects are still in memory; their resources are not |
136 | /// released). It should only be maintained if it cannot be recreated on |
137 | /// demand. One example of when the state would be maintained is if the child |
138 | /// subtree contains a [Navigator], since that widget maintains elaborate |
139 | /// state that cannot be recreated on the fly. |
140 | /// |
141 | /// If this property is true, an [Offstage] widget is used to hide the child |
142 | /// instead of replacing it with [replacement]. |
143 | /// |
144 | /// If this property is false, then [maintainAnimation] must also be false. |
145 | /// |
146 | /// Dynamically changing this value may cause the current state of the |
147 | /// subtree to be lost (and a new instance of the subtree, with new [State] |
148 | /// objects, to be immediately created if [visible] is true). |
149 | final bool maintainState; |
150 | |
151 | /// Whether to maintain animations within the [child] subtree when it is |
152 | /// not [visible]. |
153 | /// |
154 | /// To set this, [maintainState] must also be set. |
155 | /// |
156 | /// Keeping animations active when the widget is not visible is even more |
157 | /// expensive than only maintaining the state. |
158 | /// |
159 | /// One example when this might be useful is if the subtree is animating its |
160 | /// layout in time with an [AnimationController], and the result of that |
161 | /// layout is being used to influence some other logic. If this flag is false, |
162 | /// then any [AnimationController]s hosted inside the [child] subtree will be |
163 | /// muted while the [visible] flag is false. |
164 | /// |
165 | /// If this property is true, no [TickerMode] widget is used. |
166 | /// |
167 | /// If this property is false, then [maintainSize] must also be false. |
168 | /// |
169 | /// Dynamically changing this value may cause the current state of the |
170 | /// subtree to be lost (and a new instance of the subtree, with new [State] |
171 | /// objects, to be immediately created if [visible] is true). |
172 | final bool maintainAnimation; |
173 | |
174 | /// Whether to maintain space for where the widget would have been. |
175 | /// |
176 | /// To set this, [maintainAnimation] and [maintainState] must also be set. |
177 | /// |
178 | /// Maintaining the size when the widget is not [visible] is not notably more |
179 | /// expensive than just keeping animations running without maintaining the |
180 | /// size, and may in some circumstances be slightly cheaper if the subtree is |
181 | /// simple and the [visible] property is frequently toggled, since it avoids |
182 | /// triggering a layout change when the [visible] property is toggled. If the |
183 | /// [child] subtree is not trivial then it is significantly cheaper to not |
184 | /// even keep the state (see [maintainState]). |
185 | /// |
186 | /// If this property is false, [Offstage] is used. |
187 | /// |
188 | /// If this property is false, then [maintainSemantics] and |
189 | /// [maintainInteractivity] must also be false. |
190 | /// |
191 | /// Dynamically changing this value may cause the current state of the |
192 | /// subtree to be lost (and a new instance of the subtree, with new [State] |
193 | /// objects, to be immediately created if [visible] is true). |
194 | /// |
195 | /// See also: |
196 | /// |
197 | /// * [AnimatedOpacity] and [FadeTransition], which apply animations to the |
198 | /// opacity for a more subtle effect. |
199 | final bool maintainSize; |
200 | |
201 | /// Whether to maintain the semantics for the widget when it is hidden (e.g. |
202 | /// for accessibility). |
203 | /// |
204 | /// To set this, [maintainSize] must also be set. |
205 | /// |
206 | /// By default, with [maintainSemantics] set to false, the [child] is not |
207 | /// visible to accessibility tools when it is hidden from the user. If this |
208 | /// flag is set to true, then accessibility tools will report the widget as if |
209 | /// it was present. |
210 | final bool maintainSemantics; |
211 | |
212 | /// Whether to allow the widget to be interactive when hidden. |
213 | /// |
214 | /// To set this, [maintainSize] must also be set. |
215 | /// |
216 | /// By default, with [maintainInteractivity] set to false, touch events cannot |
217 | /// reach the [child] when it is hidden from the user. If this flag is set to |
218 | /// true, then touch events will nonetheless be passed through. |
219 | final bool maintainInteractivity; |
220 | |
221 | /// Tells the visibility state of an element in the tree based off its |
222 | /// ancestor [Visibility] elements. |
223 | /// |
224 | /// If there's one or more [Visibility] widgets in the ancestor tree, this |
225 | /// will return true if and only if all of those widgets have [visible] set |
226 | /// to true. If there is no [Visibility] widget in the ancestor tree of the |
227 | /// specified build context, this will return true. |
228 | /// |
229 | /// This will register a dependency from the specified context on any |
230 | /// [Visibility] elements in the ancestor tree, such that if any of their |
231 | /// visibilities changes, the specified context will be rebuilt. |
232 | static bool of(BuildContext context) { |
233 | bool isVisible = true; |
234 | BuildContext ancestorContext = context; |
235 | InheritedElement? ancestor = ancestorContext.getElementForInheritedWidgetOfExactType<_VisibilityScope>(); |
236 | while (isVisible && ancestor != null) { |
237 | final _VisibilityScope scope = context.dependOnInheritedElement(ancestor) as _VisibilityScope; |
238 | isVisible = scope.isVisible; |
239 | ancestor.visitAncestorElements((Element parent) { |
240 | ancestorContext = parent; |
241 | return false; |
242 | }); |
243 | ancestor = ancestorContext.getElementForInheritedWidgetOfExactType<_VisibilityScope>(); |
244 | } |
245 | return isVisible; |
246 | } |
247 | |
248 | @override |
249 | Widget build(BuildContext context) { |
250 | Widget result = child; |
251 | if (maintainSize) { |
252 | result = _Visibility( |
253 | visible: visible, |
254 | maintainSemantics: maintainSemantics, |
255 | child: IgnorePointer( |
256 | ignoring: !visible && !maintainInteractivity, |
257 | child: result, |
258 | ), |
259 | ); |
260 | } else { |
261 | assert(!maintainInteractivity); |
262 | assert(!maintainSemantics); |
263 | assert(!maintainSize); |
264 | if (maintainState) { |
265 | if (!maintainAnimation) { |
266 | result = TickerMode(enabled: visible, child: result); |
267 | } |
268 | result = Offstage( |
269 | offstage: !visible, |
270 | child: result, |
271 | ); |
272 | } else { |
273 | assert(!maintainAnimation); |
274 | assert(!maintainState); |
275 | result = visible ? child : replacement; |
276 | } |
277 | } |
278 | return _VisibilityScope(isVisible: visible, child: result); |
279 | } |
280 | |
281 | @override |
282 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
283 | super.debugFillProperties(properties); |
284 | properties.add(FlagProperty('visible' , value: visible, ifFalse: 'hidden' , ifTrue: 'visible' )); |
285 | properties.add(FlagProperty('maintainState' , value: maintainState, ifFalse: 'maintainState' )); |
286 | properties.add(FlagProperty('maintainAnimation' , value: maintainAnimation, ifFalse: 'maintainAnimation' )); |
287 | properties.add(FlagProperty('maintainSize' , value: maintainSize, ifFalse: 'maintainSize' )); |
288 | properties.add(FlagProperty('maintainSemantics' , value: maintainSemantics, ifFalse: 'maintainSemantics' )); |
289 | properties.add(FlagProperty('maintainInteractivity' , value: maintainInteractivity, ifFalse: 'maintainInteractivity' )); |
290 | } |
291 | } |
292 | |
293 | /// Inherited widget that allows descendants to find their visibility status. |
294 | class _VisibilityScope extends InheritedWidget { |
295 | const _VisibilityScope({required this.isVisible, required super.child}); |
296 | |
297 | final bool isVisible; |
298 | |
299 | @override |
300 | bool updateShouldNotify(_VisibilityScope old) { |
301 | return isVisible != old.isVisible; |
302 | } |
303 | } |
304 | |
305 | /// Whether to show or hide a sliver child. |
306 | /// |
307 | /// By default, the [visible] property controls whether the [sliver] is included |
308 | /// in the subtree or not; when it is not [visible], the [replacementSliver] is |
309 | /// included instead. |
310 | /// |
311 | /// A variety of flags can be used to tweak exactly how the sliver is hidden. |
312 | /// (Changing the flags dynamically is discouraged, as it can cause the [sliver] |
313 | /// subtree to be rebuilt, with any state in the subtree being discarded. |
314 | /// Typically, only the [visible] flag is changed dynamically.) |
315 | /// |
316 | /// These widgets provide some of the facets of this one: |
317 | /// |
318 | /// * [SliverOpacity], which can stop its sliver child from being painted. |
319 | /// * [SliverOffstage], which can stop its sliver child from being laid out or |
320 | /// painted. |
321 | /// * [TickerMode], which can stop its child from being animated. |
322 | /// * [ExcludeSemantics], which can hide the child from accessibility tools. |
323 | /// * [SliverIgnorePointer], which can disable touch interactions with the |
324 | /// sliver child. |
325 | /// |
326 | /// Using this widget is not necessary to hide children. The simplest way to |
327 | /// hide a child is just to not include it. If a child _must_ be given (e.g. |
328 | /// because the parent is a [StatelessWidget]), then including a childless |
329 | /// [SliverToBoxAdapter] instead of the child that would otherwise be included |
330 | /// is typically more efficient than using [SliverVisibility]. |
331 | /// |
332 | /// See also: |
333 | /// |
334 | /// * [Visibility], the equivalent widget for boxes. |
335 | class SliverVisibility extends StatelessWidget { |
336 | /// Control whether the given [sliver] is [visible]. |
337 | /// |
338 | /// The [maintainSemantics] and [maintainInteractivity] arguments can only be |
339 | /// set if [maintainSize] is set. |
340 | /// |
341 | /// The [maintainSize] argument can only be set if [maintainAnimation] is set. |
342 | /// |
343 | /// The [maintainAnimation] argument can only be set if [maintainState] is |
344 | /// set. |
345 | const SliverVisibility({ |
346 | super.key, |
347 | required this.sliver, |
348 | this.replacementSliver = const SliverToBoxAdapter(), |
349 | this.visible = true, |
350 | this.maintainState = false, |
351 | this.maintainAnimation = false, |
352 | this.maintainSize = false, |
353 | this.maintainSemantics = false, |
354 | this.maintainInteractivity = false, |
355 | }) : assert( |
356 | maintainState || !maintainAnimation, |
357 | 'Cannot maintain animations if the state is not also maintained.' , |
358 | ), |
359 | assert( |
360 | maintainAnimation || !maintainSize, |
361 | 'Cannot maintain size if animations are not maintained.' , |
362 | ), |
363 | assert( |
364 | maintainSize || !maintainSemantics, |
365 | 'Cannot maintain semantics if size is not maintained.' , |
366 | ), |
367 | assert( |
368 | maintainSize || !maintainInteractivity, |
369 | 'Cannot maintain interactivity if size is not maintained.' , |
370 | ); |
371 | |
372 | /// Control whether the given [sliver] is [visible]. |
373 | /// |
374 | /// This is equivalent to the default [SliverVisibility] constructor with all |
375 | /// "maintain" fields set to true. This constructor should be used in place of |
376 | /// a [SliverOpacity] widget that only takes on values of `0.0` or `1.0`, as it |
377 | /// avoids extra compositing when fully opaque. |
378 | const SliverVisibility.maintain({ |
379 | super.key, |
380 | required this.sliver, |
381 | this.replacementSliver = const SliverToBoxAdapter(), |
382 | this.visible = true, |
383 | }) : maintainState = true, |
384 | maintainAnimation = true, |
385 | maintainSize = true, |
386 | maintainSemantics = true, |
387 | maintainInteractivity = true; |
388 | |
389 | /// The sliver to show or hide, as controlled by [visible]. |
390 | final Widget sliver; |
391 | |
392 | /// The widget to use when the sliver child is not [visible], assuming that |
393 | /// none of the `maintain` flags (in particular, [maintainState]) are set. |
394 | /// |
395 | /// The normal behavior is to replace the widget with a childless |
396 | /// [SliverToBoxAdapter], which by default has a geometry of |
397 | /// [SliverGeometry.zero]. |
398 | final Widget replacementSliver; |
399 | |
400 | /// Switches between showing the [sliver] or hiding it. |
401 | /// |
402 | /// The `maintain` flags should be set to the same values regardless of the |
403 | /// state of the [visible] property, otherwise they will not operate correctly |
404 | /// (specifically, the state will be lost regardless of the state of |
405 | /// [maintainState] whenever any of the `maintain` flags are changed, since |
406 | /// doing so will result in a subtree shape change). |
407 | /// |
408 | /// Unless [maintainState] is set, the [sliver] subtree will be disposed |
409 | /// (removed from the tree) while hidden. |
410 | final bool visible; |
411 | |
412 | /// Whether to maintain the [State] objects of the [sliver] subtree when it is |
413 | /// not [visible]. |
414 | /// |
415 | /// Keeping the state of the subtree is potentially expensive (because it |
416 | /// means all the objects are still in memory; their resources are not |
417 | /// released). It should only be maintained if it cannot be recreated on |
418 | /// demand. One example of when the state would be maintained is if the sliver |
419 | /// subtree contains a [Navigator], since that widget maintains elaborate |
420 | /// state that cannot be recreated on the fly. |
421 | /// |
422 | /// If this property is true, a [SliverOffstage] widget is used to hide the |
423 | /// sliver instead of replacing it with [replacementSliver]. |
424 | /// |
425 | /// If this property is false, then [maintainAnimation] must also be false. |
426 | /// |
427 | /// Dynamically changing this value may cause the current state of the |
428 | /// subtree to be lost (and a new instance of the subtree, with new [State] |
429 | /// objects, to be immediately created if [visible] is true). |
430 | final bool maintainState; |
431 | |
432 | /// Whether to maintain animations within the [sliver] subtree when it is |
433 | /// not [visible]. |
434 | /// |
435 | /// To set this, [maintainState] must also be set. |
436 | /// |
437 | /// Keeping animations active when the widget is not visible is even more |
438 | /// expensive than only maintaining the state. |
439 | /// |
440 | /// One example when this might be useful is if the subtree is animating its |
441 | /// layout in time with an [AnimationController], and the result of that |
442 | /// layout is being used to influence some other logic. If this flag is false, |
443 | /// then any [AnimationController]s hosted inside the [sliver] subtree will be |
444 | /// muted while the [visible] flag is false. |
445 | /// |
446 | /// If this property is true, no [TickerMode] widget is used. |
447 | /// |
448 | /// If this property is false, then [maintainSize] must also be false. |
449 | /// |
450 | /// Dynamically changing this value may cause the current state of the |
451 | /// subtree to be lost (and a new instance of the subtree, with new [State] |
452 | /// objects, to be immediately created if [visible] is true). |
453 | final bool maintainAnimation; |
454 | |
455 | /// Whether to maintain space for where the sliver would have been. |
456 | /// |
457 | /// To set this, [maintainAnimation] must also be set. |
458 | /// |
459 | /// Maintaining the size when the sliver is not [visible] is not notably more |
460 | /// expensive than just keeping animations running without maintaining the |
461 | /// size, and may in some circumstances be slightly cheaper if the subtree is |
462 | /// simple and the [visible] property is frequently toggled, since it avoids |
463 | /// triggering a layout change when the [visible] property is toggled. If the |
464 | /// [sliver] subtree is not trivial then it is significantly cheaper to not |
465 | /// even keep the state (see [maintainState]). |
466 | /// |
467 | /// If this property is false, [SliverOffstage] is used. |
468 | /// |
469 | /// If this property is false, then [maintainSemantics] and |
470 | /// [maintainInteractivity] must also be false. |
471 | /// |
472 | /// Dynamically changing this value may cause the current state of the |
473 | /// subtree to be lost (and a new instance of the subtree, with new [State] |
474 | /// objects, to be immediately created if [visible] is true). |
475 | final bool maintainSize; |
476 | |
477 | /// Whether to maintain the semantics for the sliver when it is hidden (e.g. |
478 | /// for accessibility). |
479 | /// |
480 | /// To set this, [maintainSize] must also be set. |
481 | /// |
482 | /// By default, with [maintainSemantics] set to false, the [sliver] is not |
483 | /// visible to accessibility tools when it is hidden from the user. If this |
484 | /// flag is set to true, then accessibility tools will report the widget as if |
485 | /// it was present. |
486 | final bool maintainSemantics; |
487 | |
488 | /// Whether to allow the sliver to be interactive when hidden. |
489 | /// |
490 | /// To set this, [maintainSize] must also be set. |
491 | /// |
492 | /// By default, with [maintainInteractivity] set to false, touch events cannot |
493 | /// reach the [sliver] when it is hidden from the user. If this flag is set to |
494 | /// true, then touch events will nonetheless be passed through. |
495 | final bool maintainInteractivity; |
496 | |
497 | @override |
498 | Widget build(BuildContext context) { |
499 | if (maintainSize) { |
500 | Widget result = sliver; |
501 | result = SliverIgnorePointer( |
502 | ignoring: !visible && !maintainInteractivity, |
503 | sliver: result, |
504 | ); |
505 | return _SliverVisibility( |
506 | visible: visible, |
507 | maintainSemantics: maintainSemantics, |
508 | sliver: result, |
509 | ); |
510 | } |
511 | assert(!maintainInteractivity); |
512 | assert(!maintainSemantics); |
513 | assert(!maintainSize); |
514 | if (maintainState) { |
515 | Widget result = sliver; |
516 | if (!maintainAnimation) { |
517 | result = TickerMode(enabled: visible, child: sliver); |
518 | } |
519 | return SliverOffstage( |
520 | sliver: result, |
521 | offstage: !visible, |
522 | ); |
523 | } |
524 | assert(!maintainAnimation); |
525 | assert(!maintainState); |
526 | return visible ? sliver : replacementSliver; |
527 | } |
528 | |
529 | @override |
530 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
531 | super.debugFillProperties(properties); |
532 | properties.add(FlagProperty('visible' , value: visible, ifFalse: 'hidden' , ifTrue: 'visible' )); |
533 | properties.add(FlagProperty('maintainState' , value: maintainState, ifFalse: 'maintainState' )); |
534 | properties.add(FlagProperty('maintainAnimation' , value: maintainAnimation, ifFalse: 'maintainAnimation' )); |
535 | properties.add(FlagProperty('maintainSize' , value: maintainSize, ifFalse: 'maintainSize' )); |
536 | properties.add(FlagProperty('maintainSemantics' , value: maintainSemantics, ifFalse: 'maintainSemantics' )); |
537 | properties.add(FlagProperty('maintainInteractivity' , value: maintainInteractivity, ifFalse: 'maintainInteractivity' )); |
538 | } |
539 | } |
540 | |
541 | // A widget that conditionally hides its child, but without the forced compositing of `Opacity`. |
542 | // |
543 | // A fully opaque `Opacity` widget is required to leave its opacity layer in the layer tree. This |
544 | // forces all parent render objects to also composite, which can break a simple scene into many |
545 | // different layers. This can be significantly more expensive, so the issue is avoided by a |
546 | // specialized render object that does not ever force compositing. |
547 | class _Visibility extends SingleChildRenderObjectWidget { |
548 | const _Visibility({ required this.visible, required this.maintainSemantics, super.child }); |
549 | |
550 | final bool visible; |
551 | final bool maintainSemantics; |
552 | |
553 | @override |
554 | _RenderVisibility createRenderObject(BuildContext context) { |
555 | return _RenderVisibility(visible, maintainSemantics); |
556 | } |
557 | |
558 | @override |
559 | void updateRenderObject(BuildContext context, _RenderVisibility renderObject) { |
560 | renderObject |
561 | ..visible = visible |
562 | ..maintainSemantics = maintainSemantics; |
563 | } |
564 | } |
565 | |
566 | class _RenderVisibility extends RenderProxyBox { |
567 | _RenderVisibility(this._visible, this._maintainSemantics); |
568 | |
569 | bool get visible => _visible; |
570 | bool _visible; |
571 | set visible(bool value) { |
572 | if (value == visible) { |
573 | return; |
574 | } |
575 | _visible = value; |
576 | markNeedsPaint(); |
577 | } |
578 | |
579 | bool get maintainSemantics => _maintainSemantics; |
580 | bool _maintainSemantics; |
581 | set maintainSemantics(bool value) { |
582 | if (value == maintainSemantics) { |
583 | return; |
584 | } |
585 | _maintainSemantics = value; |
586 | markNeedsSemanticsUpdate(); |
587 | } |
588 | |
589 | @override |
590 | void visitChildrenForSemantics(RenderObjectVisitor visitor) { |
591 | if (maintainSemantics || visible) { |
592 | super.visitChildrenForSemantics(visitor); |
593 | } |
594 | } |
595 | |
596 | @override |
597 | void paint(PaintingContext context, Offset offset) { |
598 | if (!visible) { |
599 | return; |
600 | } |
601 | super.paint(context, offset); |
602 | } |
603 | } |
604 | |
605 | // A widget that conditionally hides its child, but without the forced compositing of `SliverOpacity`. |
606 | // |
607 | // A fully opaque `SliverOpacity` widget is required to leave its opacity layer in the layer tree. |
608 | // This forces all parent render objects to also composite, which can break a simple scene into many |
609 | // different layers. This can be significantly more expensive, so the issue is avoided by a |
610 | // specialized render object that does not ever force compositing. |
611 | class _SliverVisibility extends SingleChildRenderObjectWidget { |
612 | const _SliverVisibility({ required this.visible, required this.maintainSemantics, Widget? sliver }) |
613 | : super(child: sliver); |
614 | |
615 | final bool visible; |
616 | final bool maintainSemantics; |
617 | |
618 | @override |
619 | RenderObject createRenderObject(BuildContext context) { |
620 | return _RenderSliverVisibility(visible, maintainSemantics); |
621 | } |
622 | |
623 | @override |
624 | void updateRenderObject(BuildContext context, _RenderSliverVisibility renderObject) { |
625 | renderObject |
626 | ..visible = visible |
627 | ..maintainSemantics = maintainSemantics; |
628 | } |
629 | } |
630 | |
631 | class _RenderSliverVisibility extends RenderProxySliver { |
632 | _RenderSliverVisibility(this._visible, this._maintainSemantics); |
633 | |
634 | bool get visible => _visible; |
635 | bool _visible; |
636 | set visible(bool value) { |
637 | if (value == visible) { |
638 | return; |
639 | } |
640 | _visible = value; |
641 | markNeedsPaint(); |
642 | } |
643 | |
644 | bool get maintainSemantics => _maintainSemantics; |
645 | bool _maintainSemantics; |
646 | set maintainSemantics(bool value) { |
647 | if (value == maintainSemantics) { |
648 | return; |
649 | } |
650 | _maintainSemantics = value; |
651 | markNeedsSemanticsUpdate(); |
652 | } |
653 | |
654 | @override |
655 | void visitChildrenForSemantics(RenderObjectVisitor visitor) { |
656 | if (maintainSemantics || visible) { |
657 | super.visitChildrenForSemantics(visitor); |
658 | } |
659 | } |
660 | |
661 | @override |
662 | void paint(PaintingContext context, Offset offset) { |
663 | if (!visible) { |
664 | return; |
665 | } |
666 | super.paint(context, offset); |
667 | } |
668 | } |
669 | |