blob: 09f040fb06f2f642fd04016ec3a1dcf603abe83c [file] [log] [blame]
// 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/rendering.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({
super.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.',
);
/// Control whether the given [child] is [visible].
///
/// The [child] and [replacement] arguments must not be null.
///
/// This is equivalent to the default [Visibility] constructor with all
/// "maintain" fields set to true. This constructor should be used in place of
/// an [Opacity] widget that only takes on values of `0.0` or `1.0`, as it
/// avoids extra compositing when fully opaque.
const Visibility.maintain({
super.key,
required this.child,
this.replacement = const SizedBox.shrink(),
this.visible = true,
}) : assert(child != null),
assert(replacement != null),
assert(visible != null),
maintainState = true,
maintainAnimation = true,
maintainSize = true,
maintainSemantics = true,
maintainInteractivity = true;
/// 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 false, [Offstage] is used.
///
/// 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(
ignoring: !visible,
ignoringSemantics: !visible && !maintainSemantics,
child: child,
);
}
return _Visibility(
visible: visible,
maintainSemantics: maintainSemantics,
child: result,
);
}
assert(!maintainInteractivity);
assert(!maintainSemantics);
assert(!maintainSize);
if (maintainState) {
Widget result = child;
if (!maintainAnimation) {
result = TickerMode(enabled: visible, child: child);
}
return Offstage(
offstage: !visible,
child: result,
);
}
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({
super.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.',
);
/// Control whether the given [sliver] is [visible].
///
/// The [sliver] and [replacementSliver] arguments must not be null.
///
/// This is equivalent to the default [SliverVisibility] constructor with all
/// "maintain" fields set to true. This constructor should be used in place of
/// a [SliverOpacity] widget that only takes on values of `0.0` or `1.0`, as it
/// avoids extra compositing when fully opaque.
const SliverVisibility.maintain({
super.key,
required this.sliver,
this.replacementSliver = const SliverToBoxAdapter(),
this.visible = true,
}) : assert(sliver != null),
assert(replacementSliver != null),
assert(visible != null),
maintainState = true,
maintainAnimation = true,
maintainSize = true,
maintainSemantics = true,
maintainInteractivity = true;
/// 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 false, [SliverOffstage] is used.
///
/// 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 _SliverVisibility(
visible: visible,
maintainSemantics: maintainSemantics,
sliver: result,
);
}
assert(!maintainInteractivity);
assert(!maintainSemantics);
assert(!maintainSize);
if (maintainState) {
Widget result = sliver;
if (!maintainAnimation) {
result = TickerMode(enabled: visible, child: sliver);
}
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'));
}
}
// A widget that conditionally hides its child, but without the forced compositing of `Opacity`.
//
// A fully opaque `Opacity` widget is required to leave its opacity layer in the layer tree. This
// forces all parent render objects to also composite, which can break a simple scene into many
// different layers. This can be significantly more expensive, so the issue is avoided by a
// specialized render object that does not ever force compositing.
class _Visibility extends SingleChildRenderObjectWidget {
const _Visibility({ required this.visible, required this.maintainSemantics, super.child });
final bool visible;
final bool maintainSemantics;
@override
RenderObject createRenderObject(BuildContext context) {
return _RenderVisibility(visible, maintainSemantics);
}
@override
void updateRenderObject(BuildContext context, covariant RenderObject renderObject) {
(renderObject as _RenderVisibility)
..visible = visible
..maintainSemantics = maintainSemantics;
}
}
class _RenderVisibility extends RenderProxyBox {
_RenderVisibility(this._visible, this._maintainSemantics);
bool get visible => _visible;
bool _visible;
set visible(bool value) {
if (value == visible) {
return;
}
_visible = value;
markNeedsPaint();
}
bool get maintainSemantics => _maintainSemantics;
bool _maintainSemantics;
set maintainSemantics(bool value) {
if (value == maintainSemantics) {
return;
}
_maintainSemantics = value;
markNeedsSemanticsUpdate();
}
@override
void visitChildrenForSemantics(RenderObjectVisitor visitor) {
if (maintainSemantics || visible) {
super.visitChildrenForSemantics(visitor);
}
}
@override
void paint(PaintingContext context, Offset offset) {
if (!visible) {
return;
}
super.paint(context, offset);
}
}
// A widget that conditionally hides its child, but without the forced compositing of `SliverOpacity`.
//
// A fully opaque `SliverOpacity` widget is required to leave its opacity layer in the layer tree.
// This forces all parent render objects to also composite, which can break a simple scene into many
// different layers. This can be significantly more expensive, so the issue is avoided by a
// specialized render object that does not ever force compositing.
class _SliverVisibility extends SingleChildRenderObjectWidget {
const _SliverVisibility({ required this.visible, required this.maintainSemantics, Widget? sliver })
: super(child: sliver);
final bool visible;
final bool maintainSemantics;
@override
RenderObject createRenderObject(BuildContext context) {
return _RenderSliverVisibility(visible, maintainSemantics);
}
@override
void updateRenderObject(BuildContext context, covariant RenderObject renderObject) {
(renderObject as _RenderSliverVisibility)
..visible = visible
..maintainSemantics = maintainSemantics;
}
}
class _RenderSliverVisibility extends RenderProxySliver {
_RenderSliverVisibility(this._visible, this._maintainSemantics);
bool get visible => _visible;
bool _visible;
set visible(bool value) {
if (value == visible) {
return;
}
_visible = value;
markNeedsPaint();
}
bool get maintainSemantics => _maintainSemantics;
bool _maintainSemantics;
set maintainSemantics(bool value) {
if (value == maintainSemantics) {
return;
}
_maintainSemantics = value;
markNeedsSemanticsUpdate();
}
@override
void visitChildrenForSemantics(RenderObjectVisitor visitor) {
if (maintainSemantics || visible) {
super.visitChildrenForSemantics(visitor);
}
}
@override
void paint(PaintingContext context, Offset offset) {
if (!visible) {
return;
}
super.paint(context, offset);
}
}