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