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