// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'basic.dart'; import 'framework.dart'; import 'sliver.dart'; import 'ticker_provider.dart'; /// Whether to show or hide a child. /// /// By default, the [visible] property controls whether the [child] is included /// in the subtree or not; when it is not [visible], the [replacement] child /// (typically a zero-sized box) is included instead. /// /// A variety of flags can be used to tweak exactly how the child is hidden. /// (Changing the flags dynamically is discouraged, as it can cause the [child] /// subtree to be rebuilt, with any state in the subtree being discarded. /// Typically, only the [visible] flag is changed dynamically.) /// /// These widgets provide some of the facets of this one: /// /// * [Opacity], which can stop its child from being painted. /// * [Offstage], which can stop its child from being laid out or painted. /// * [TickerMode], which can stop its child from being animated. /// * [ExcludeSemantics], which can hide the child from accessibility tools. /// * [IgnorePointer], which can disable touch interactions with the child. /// /// Using this widget is not necessary to hide children. The simplest way to /// hide a child is just to not include it, or, if a child _must_ be given (e.g. /// because the parent is a [StatelessWidget]) then to use [SizedBox.shrink] /// instead of the child that would otherwise be included. /// /// See also: /// /// * [AnimatedSwitcher], which can fade from one child to the next as the /// subtree changes. /// * [AnimatedCrossFade], which can fade between two specific children. class Visibility extends StatelessWidget { /// Control whether the given [child] is [visible]. /// /// The [child] and [replacement] arguments must not be null. /// /// The boolean arguments must not be null. /// /// The [maintainSemantics] and [maintainInteractivity] arguments can only be /// set if [maintainSize] is set. /// /// The [maintainSize] argument can only be set if [maintainAnimation] is set. /// /// The [maintainAnimation] argument can only be set if [maintainState] is /// set. const Visibility({ Key? key, required this.child, this.replacement = const SizedBox.shrink(), this.visible = true, this.maintainState = false, this.maintainAnimation = false, this.maintainSize = false, this.maintainSemantics = false, this.maintainInteractivity = false, }) : assert(child != null), assert(replacement != null), assert(visible != null), assert(maintainState != null), assert(maintainAnimation != null), assert(maintainSize != null), assert( maintainState == true || maintainAnimation == false, 'Cannot maintain animations if the state is not also maintained.' ), assert( maintainAnimation == true || maintainSize == false, 'Cannot maintain size if animations are not maintained.', ), assert( maintainSize == true || maintainSemantics == false, 'Cannot maintain semantics if size is not maintained.', ), assert( maintainSize == true || maintainInteractivity == false, 'Cannot maintain interactivity if size is not maintained.', ), super(key: key); /// The widget to show or hide, as controlled by [visible]. /// /// {@macro flutter.widgets.ProxyWidget.child} final Widget child; /// The widget to use when the child is not [visible], assuming that none of /// the `maintain` flags (in particular, [maintainState]) are set. /// /// The normal behavior is to replace the widget with a zero by zero box /// ([SizedBox.shrink]). /// /// See also: /// /// * [AnimatedCrossFade], which can animate between two children. final Widget replacement; /// Switches between showing the [child] or hiding it. /// /// The `maintain` flags should be set to the same values regardless of the /// state of the [visible] property, otherwise they will not operate correctly /// (specifically, the state will be lost regardless of the state of /// [maintainState] whenever any of the `maintain` flags are changed, since /// doing so will result in a subtree shape change). /// /// Unless [maintainState] is set, the [child] subtree will be disposed /// (removed from the tree) while hidden. final bool visible; /// Whether to maintain the [State] objects of the [child] subtree when it is /// not [visible]. /// /// Keeping the state of the subtree is potentially expensive (because it /// means all the objects are still in memory; their resources are not /// released). It should only be maintained if it cannot be recreated on /// demand. One example of when the state would be maintained is if the child /// subtree contains a [Navigator], since that widget maintains elaborate /// state that cannot be recreated on the fly. /// /// If this property is true, an [Offstage] widget is used to hide the child /// instead of replacing it with [replacement]. /// /// If this property is false, then [maintainAnimation] must also be false. /// /// Dynamically changing this value may cause the current state of the /// subtree to be lost (and a new instance of the subtree, with new [State] /// objects, to be immediately created if [visible] is true). final bool maintainState; /// Whether to maintain animations within the [child] subtree when it is /// not [visible]. /// /// To set this, [maintainState] must also be set. /// /// Keeping animations active when the widget is not visible is even more /// expensive than only maintaining the state. /// /// One example when this might be useful is if the subtree is animating its /// layout in time with an [AnimationController], and the result of that /// layout is being used to influence some other logic. If this flag is false, /// then any [AnimationController]s hosted inside the [child] subtree will be /// muted while the [visible] flag is false. /// /// If this property is true, no [TickerMode] widget is used. /// /// If this property is false, then [maintainSize] must also be false. /// /// Dynamically changing this value may cause the current state of the /// subtree to be lost (and a new instance of the subtree, with new [State] /// objects, to be immediately created if [visible] is true). final bool maintainAnimation; /// Whether to maintain space for where the widget would have been. /// /// To set this, [maintainAnimation] and [maintainState] must also be set. /// /// Maintaining the size when the widget is not [visible] is not notably more /// expensive than just keeping animations running without maintaining the /// size, and may in some circumstances be slightly cheaper if the subtree is /// simple and the [visible] property is frequently toggled, since it avoids /// triggering a layout change when the [visible] property is toggled. If the /// [child] subtree is not trivial then it is significantly cheaper to not /// even keep the state (see [maintainState]). /// /// If this property is true, [Opacity] is used instead of [Offstage]. /// /// If this property is false, then [maintainSemantics] and /// [maintainInteractivity] must also be false. /// /// Dynamically changing this value may cause the current state of the /// subtree to be lost (and a new instance of the subtree, with new [State] /// objects, to be immediately created if [visible] is true). /// /// See also: /// /// * [AnimatedOpacity] and [FadeTransition], which apply animations to the /// opacity for a more subtle effect. final bool maintainSize; /// Whether to maintain the semantics for the widget when it is hidden (e.g. /// for accessibility). /// /// To set this, [maintainSize] must also be set. /// /// By default, with [maintainSemantics] set to false, the [child] is not /// visible to accessibility tools when it is hidden from the user. If this /// flag is set to true, then accessibility tools will report the widget as if /// it was present. /// /// Dynamically changing this value may cause the current state of the /// subtree to be lost (and a new instance of the subtree, with new [State] /// objects, to be immediately created if [visible] is true). final bool maintainSemantics; /// Whether to allow the widget to be interactive when hidden. /// /// To set this, [maintainSize] must also be set. /// /// By default, with [maintainInteractivity] set to false, touch events cannot /// reach the [child] when it is hidden from the user. If this flag is set to /// true, then touch events will nonetheless be passed through. /// /// Dynamically changing this value may cause the current state of the /// subtree to be lost (and a new instance of the subtree, with new [State] /// objects, to be immediately created if [visible] is true). final bool maintainInteractivity; @override Widget build(BuildContext context) { if (maintainSize) { Widget result = child; if (!maintainInteractivity) { result = IgnorePointer( child: child, ignoring: !visible, ignoringSemantics: !visible && !maintainSemantics, ); } return Opacity( opacity: visible ? 1.0 : 0.0, alwaysIncludeSemantics: maintainSemantics, child: result, ); } assert(!maintainInteractivity); assert(!maintainSemantics); assert(!maintainSize); if (maintainState) { Widget result = child; if (!maintainAnimation) result = TickerMode(child: child, enabled: visible); return Offstage( child: result, offstage: !visible, ); } assert(!maintainAnimation); assert(!maintainState); return visible ? child : replacement; } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(FlagProperty('visible', value: visible, ifFalse: 'hidden', ifTrue: 'visible')); properties.add(FlagProperty('maintainState', value: maintainState, ifFalse: 'maintainState')); properties.add(FlagProperty('maintainAnimation', value: maintainAnimation, ifFalse: 'maintainAnimation')); properties.add(FlagProperty('maintainSize', value: maintainSize, ifFalse: 'maintainSize')); properties.add(FlagProperty('maintainSemantics', value: maintainSemantics, ifFalse: 'maintainSemantics')); properties.add(FlagProperty('maintainInteractivity', value: maintainInteractivity, ifFalse: 'maintainInteractivity')); } } /// Whether to show or hide a sliver child. /// /// By default, the [visible] property controls whether the [sliver] is included /// in the subtree or not; when it is not [visible], the [replacementSliver] is /// included instead. /// /// A variety of flags can be used to tweak exactly how the sliver is hidden. /// (Changing the flags dynamically is discouraged, as it can cause the [sliver] /// subtree to be rebuilt, with any state in the subtree being discarded. /// Typically, only the [visible] flag is changed dynamically.) /// /// These widgets provide some of the facets of this one: /// /// * [SliverOpacity], which can stop its sliver child from being painted. /// * [SliverOffstage], which can stop its sliver child from being laid out or /// painted. /// * [TickerMode], which can stop its child from being animated. /// * [ExcludeSemantics], which can hide the child from accessibility tools. /// * [SliverIgnorePointer], which can disable touch interactions with the /// sliver child. /// /// Using this widget is not necessary to hide children. The simplest way to /// hide a child is just to not include it, or, if a child _must_ be given (e.g. /// because the parent is a [StatelessWidget]) then to use a childless /// [SliverToBoxAdapter] instead of the child that would otherwise be included. class SliverVisibility extends StatelessWidget { /// Control whether the given [sliver] is [visible]. /// /// The [sliver] and [replacementSliver] arguments must not be null. /// /// The boolean arguments must not be null. /// /// The [maintainSemantics] and [maintainInteractivity] arguments can only be /// set if [maintainSize] is set. /// /// The [maintainSize] argument can only be set if [maintainAnimation] is set. /// /// The [maintainAnimation] argument can only be set if [maintainState] is /// set. const SliverVisibility({ Key? key, required this.sliver, this.replacementSliver = const SliverToBoxAdapter(), this.visible = true, this.maintainState = false, this.maintainAnimation = false, this.maintainSize = false, this.maintainSemantics = false, this.maintainInteractivity = false, }) : assert(sliver != null), assert(replacementSliver != null), assert(visible != null), assert(maintainState != null), assert(maintainAnimation != null), assert(maintainSize != null), assert(maintainSemantics != null), assert(maintainInteractivity != null), assert( maintainState == true || maintainAnimation == false, 'Cannot maintain animations if the state is not also maintained.', ), assert( maintainAnimation == true || maintainSize == false, 'Cannot maintain size if animations are not maintained.', ), assert( maintainSize == true || maintainSemantics == false, 'Cannot maintain semantics if size is not maintained.', ), assert( maintainSize == true || maintainInteractivity == false, 'Cannot maintain interactivity if size is not maintained.', ), super(key: key); /// The sliver to show or hide, as controlled by [visible]. final Widget sliver; /// The widget to use when the sliver child is not [visible], assuming that /// none of the `maintain` flags (in particular, [maintainState]) are set. /// /// The normal behavior is to replace the widget with a childless /// [SliverToBoxAdapter], which by default has a geometry of /// [SliverGeometry.zero]. final Widget replacementSliver; /// Switches between showing the [sliver] or hiding it. /// /// The `maintain` flags should be set to the same values regardless of the /// state of the [visible] property, otherwise they will not operate correctly /// (specifically, the state will be lost regardless of the state of /// [maintainState] whenever any of the `maintain` flags are changed, since /// doing so will result in a subtree shape change). /// /// Unless [maintainState] is set, the [sliver] subtree will be disposed /// (removed from the tree) while hidden. final bool visible; /// Whether to maintain the [State] objects of the [sliver] subtree when it is /// not [visible]. /// /// Keeping the state of the subtree is potentially expensive (because it /// means all the objects are still in memory; their resources are not /// released). It should only be maintained if it cannot be recreated on /// demand. One example of when the state would be maintained is if the sliver /// subtree contains a [Navigator], since that widget maintains elaborate /// state that cannot be recreated on the fly. /// /// If this property is true, a [SliverOffstage] widget is used to hide the /// sliver instead of replacing it with [replacementSliver]. /// /// If this property is false, then [maintainAnimation] must also be false. /// /// Dynamically changing this value may cause the current state of the /// subtree to be lost (and a new instance of the subtree, with new [State] /// objects, to be immediately created if [visible] is true). final bool maintainState; /// Whether to maintain animations within the [sliver] subtree when it is /// not [visible]. /// /// To set this, [maintainState] must also be set. /// /// Keeping animations active when the widget is not visible is even more /// expensive than only maintaining the state. /// /// One example when this might be useful is if the subtree is animating its /// layout in time with an [AnimationController], and the result of that /// layout is being used to influence some other logic. If this flag is false, /// then any [AnimationController]s hosted inside the [sliver] subtree will be /// muted while the [visible] flag is false. /// /// If this property is true, no [TickerMode] widget is used. /// /// If this property is false, then [maintainSize] must also be false. /// /// Dynamically changing this value may cause the current state of the /// subtree to be lost (and a new instance of the subtree, with new [State] /// objects, to be immediately created if [visible] is true). final bool maintainAnimation; /// Whether to maintain space for where the sliver would have been. /// /// To set this, [maintainAnimation] must also be set. /// /// Maintaining the size when the sliver is not [visible] is not notably more /// expensive than just keeping animations running without maintaining the /// size, and may in some circumstances be slightly cheaper if the subtree is /// simple and the [visible] property is frequently toggled, since it avoids /// triggering a layout change when the [visible] property is toggled. If the /// [sliver] subtree is not trivial then it is significantly cheaper to not /// even keep the state (see [maintainState]). /// /// If this property is true, [SliverOpacity] is used instead of /// [SliverOffstage]. /// /// If this property is false, then [maintainSemantics] and /// [maintainInteractivity] must also be false. /// /// Dynamically changing this value may cause the current state of the /// subtree to be lost (and a new instance of the subtree, with new [State] /// objects, to be immediately created if [visible] is true). final bool maintainSize; /// Whether to maintain the semantics for the sliver when it is hidden (e.g. /// for accessibility). /// /// To set this, [maintainSize] must also be set. /// /// By default, with [maintainSemantics] set to false, the [sliver] is not /// visible to accessibility tools when it is hidden from the user. If this /// flag is set to true, then accessibility tools will report the widget as if /// it was present. /// /// Dynamically changing this value may cause the current state of the /// subtree to be lost (and a new instance of the subtree, with new [State] /// objects, to be immediately created if [visible] is true). final bool maintainSemantics; /// Whether to allow the sliver to be interactive when hidden. /// /// To set this, [maintainSize] must also be set. /// /// By default, with [maintainInteractivity] set to false, touch events cannot /// reach the [sliver] when it is hidden from the user. If this flag is set to /// true, then touch events will nonetheless be passed through. /// /// Dynamically changing this value may cause the current state of the /// subtree to be lost (and a new instance of the subtree, with new [State] /// objects, to be immediately created if [visible] is true). final bool maintainInteractivity; @override Widget build(BuildContext context) { if (maintainSize) { Widget result = sliver; if (!maintainInteractivity) { result = SliverIgnorePointer( sliver: sliver, ignoring: !visible, ignoringSemantics: !visible && !maintainSemantics, ); } return SliverOpacity( opacity: visible ? 1.0 : 0.0, alwaysIncludeSemantics: maintainSemantics, sliver: result, ); } assert(!maintainInteractivity); assert(!maintainSemantics); assert(!maintainSize); if (maintainState) { Widget result = sliver; if (!maintainAnimation) result = TickerMode(child: sliver, enabled: visible); return SliverOffstage( sliver: result, offstage: !visible, ); } assert(!maintainAnimation); assert(!maintainState); return visible ? sliver : replacementSliver; } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(FlagProperty('visible', value: visible, ifFalse: 'hidden', ifTrue: 'visible')); properties.add(FlagProperty('maintainState', value: maintainState, ifFalse: 'maintainState')); properties.add(FlagProperty('maintainAnimation', value: maintainAnimation, ifFalse: 'maintainAnimation')); properties.add(FlagProperty('maintainSize', value: maintainSize, ifFalse: 'maintainSize')); properties.add(FlagProperty('maintainSemantics', value: maintainSemantics, ifFalse: 'maintainSemantics')); properties.add(FlagProperty('maintainInteractivity', value: maintainInteractivity, ifFalse: 'maintainInteractivity')); } }