[framework] use Visibility instead of Opacity (#112191)
diff --git a/packages/flutter/lib/src/cupertino/context_menu.dart b/packages/flutter/lib/src/cupertino/context_menu.dart
index ac9be94..bd24142 100644
--- a/packages/flutter/lib/src/cupertino/context_menu.dart
+++ b/packages/flutter/lib/src/cupertino/context_menu.dart
@@ -378,9 +378,9 @@
onTap: _onTap,
child: TickerMode(
enabled: !_childHidden,
- child: Opacity(
+ child: Visibility.maintain(
key: _childGlobalKey,
- opacity: _childHidden ? 0.0 : 1.0,
+ visible: !_childHidden,
child: widget.child,
),
),
diff --git a/packages/flutter/lib/src/material/bottom_navigation_bar.dart b/packages/flutter/lib/src/material/bottom_navigation_bar.dart
index 72c87ba..ced7dc5 100644
--- a/packages/flutter/lib/src/material/bottom_navigation_bar.dart
+++ b/packages/flutter/lib/src/material/bottom_navigation_bar.dart
@@ -728,9 +728,8 @@
if (!showUnselectedLabels && !showSelectedLabels) {
// Never show any labels.
- text = Opacity(
- alwaysIncludeSemantics: true,
- opacity: 0.0,
+ text = Visibility.maintain(
+ visible: false,
child: text,
);
} else if (!showUnselectedLabels) {
diff --git a/packages/flutter/lib/src/material/navigation_rail.dart b/packages/flutter/lib/src/material/navigation_rail.dart
index 89efd97..8120e16 100644
--- a/packages/flutter/lib/src/material/navigation_rail.dart
+++ b/packages/flutter/lib/src/material/navigation_rail.dart
@@ -619,9 +619,8 @@
SizedBox(
width: 0,
height: 0,
- child: Opacity(
- alwaysIncludeSemantics: true,
- opacity: 0.0,
+ child: Visibility.maintain(
+ visible: false,
child: label,
),
),
diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart
index 04f8610..693af83 100644
--- a/packages/flutter/lib/src/widgets/basic.dart
+++ b/packages/flutter/lib/src/widgets/basic.dart
@@ -289,7 +289,9 @@
///
/// * [Visibility], which can hide a child more efficiently (albeit less
/// subtly, because it is either visible or hidden, rather than allowing
-/// fractional opacity values).
+/// fractional opacity values). Specifically, the [Visibility.maintain]
+/// constructor is equivalent to using an opacity widget with values of
+/// `0.0` or `1.0`.
/// * [ShaderMask], which can apply more elaborate effects to its child.
/// * [Transform], which applies an arbitrary transform to its child widget at
/// paint time.
diff --git a/packages/flutter/lib/src/widgets/sliver.dart b/packages/flutter/lib/src/widgets/sliver.dart
index 69cacdb..077f5d9 100644
--- a/packages/flutter/lib/src/widgets/sliver.dart
+++ b/packages/flutter/lib/src/widgets/sliver.dart
@@ -1656,6 +1656,11 @@
/// RenderBox layout protocol.
/// * [AnimatedOpacity], which uses an animation internally to efficiently
/// animate [Opacity].
+/// * [SliverVisibility], which can hide a child more efficiently (albeit less
+/// subtly, because it is either visible or hidden, rather than allowing
+/// fractional opacity values). Specifically, the [SliverVisibility.maintain]
+/// constructor is equivalent to using a sliver opacity widget with values of
+/// `0.0` or `1.0`.
class SliverOpacity extends SingleChildRenderObjectWidget {
/// Creates a sliver that makes its sliver child partially transparent.
///
diff --git a/packages/flutter/lib/src/widgets/visibility.dart b/packages/flutter/lib/src/widgets/visibility.dart
index 5c8b91a..09f040f 100644
--- a/packages/flutter/lib/src/widgets/visibility.dart
+++ b/packages/flutter/lib/src/widgets/visibility.dart
@@ -85,6 +85,28 @@
'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}
@@ -332,6 +354,28 @@
'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;
diff --git a/packages/flutter/test/material/bottom_navigation_bar_test.dart b/packages/flutter/test/material/bottom_navigation_bar_test.dart
index e79dd93..6419252 100644
--- a/packages/flutter/test/material/bottom_navigation_bar_test.dart
+++ b/packages/flutter/test/material/bottom_navigation_bar_test.dart
@@ -1971,8 +1971,8 @@
await tester.pumpWidget(widget);
expect(find.text('Red'), findsOneWidget);
expect(find.text('Green'), findsOneWidget);
- expect(tester.widget<Opacity>(find.byType(Opacity).first).opacity, 0.0);
- expect(tester.widget<Opacity>(find.byType(Opacity).last).opacity, 0.0);
+ expect(tester.widget<Visibility>(find.byType(Visibility).first).visible, false);
+ expect(tester.widget<Visibility>(find.byType(Visibility).last).visible, false);
},
);
@@ -2009,8 +2009,8 @@
);
expect(find.text('Red'), findsOneWidget);
expect(find.text('Green'), findsOneWidget);
- expect(tester.widget<Opacity>(find.byType(Opacity).first).opacity, 0.0);
- expect(tester.widget<Opacity>(find.byType(Opacity).last).opacity, 0.0);
+ expect(tester.widget<Visibility>(find.byType(Visibility).first).visible, false);
+ expect(tester.widget<Visibility>(find.byType(Visibility).last).visible, false);
},
);
diff --git a/packages/flutter/test/material/bottom_navigation_bar_theme_test.dart b/packages/flutter/test/material/bottom_navigation_bar_theme_test.dart
index 578f2cb..817dd5f3 100644
--- a/packages/flutter/test/material/bottom_navigation_bar_theme_test.dart
+++ b/packages/flutter/test/material/bottom_navigation_bar_theme_test.dart
@@ -359,14 +359,14 @@
);
- final Finder findOpacity = find.descendant(
+ final Finder findVisibility = find.descendant(
of: find.byType(BottomNavigationBar),
- matching: find.byType(Opacity),
+ matching: find.byType(Visibility),
);
- expect(findOpacity, findsNWidgets(2));
- expect(tester.widget<Opacity>(findOpacity.at(0)).opacity, 0.0);
- expect(tester.widget<Opacity>(findOpacity.at(1)).opacity, 0.0);
+ expect(findVisibility, findsNWidgets(2));
+ expect(tester.widget<Visibility>(findVisibility.at(0)).visible, false);
+ expect(tester.widget<Visibility>(findVisibility.at(1)).visible, false);
});
testWidgets('BottomNavigationBarTheme can be used to hide selected labels', (WidgetTester tester) async {
diff --git a/packages/flutter/test/material/navigation_rail_test.dart b/packages/flutter/test/material/navigation_rail_test.dart
index 17eb179..a0b567f 100644
--- a/packages/flutter/test/material/navigation_rail_test.dart
+++ b/packages/flutter/test/material/navigation_rail_test.dart
@@ -4693,14 +4693,14 @@
// Only valid when labelType != all.
double? _labelOpacity(WidgetTester tester, String text) {
- // We search for both Opacity and FadeTransition since in some
+ // We search for both Visibility and FadeTransition since in some
// cases opacity is animated, in other it's not.
- final Iterable<Opacity> opacityWidgets = tester.widgetList<Opacity>(find.ancestor(
+ final Iterable<Visibility> visibilityWidgets = tester.widgetList<Visibility>(find.ancestor(
of: find.text(text),
- matching: find.byType(Opacity),
+ matching: find.byType(Visibility),
));
- if (opacityWidgets.isNotEmpty) {
- return opacityWidgets.single.opacity;
+ if (visibilityWidgets.isNotEmpty) {
+ return visibilityWidgets.single.visible ? 1.0 : 0.0;
}
final FadeTransition fadeTransitionWidget = tester.widget<FadeTransition>(
diff --git a/packages/flutter/test/material/navigation_rail_theme_test.dart b/packages/flutter/test/material/navigation_rail_theme_test.dart
index fe76502..6f0ff40 100644
--- a/packages/flutter/test/material/navigation_rail_theme_test.dart
+++ b/packages/flutter/test/material/navigation_rail_theme_test.dart
@@ -363,27 +363,27 @@
}
NavigationRailLabelType _labelType(WidgetTester tester) {
- if (_opacityAboveLabel('Abc').evaluate().isNotEmpty && _opacityAboveLabel('Def').evaluate().isNotEmpty) {
- return _labelOpacity(tester, 'Abc') == 1 ? NavigationRailLabelType.selected : NavigationRailLabelType.none;
+ if (_visibilityAboveLabel('Abc').evaluate().isNotEmpty && _visibilityAboveLabel('Def').evaluate().isNotEmpty) {
+ return _labelVisibility(tester, 'Abc') ? NavigationRailLabelType.selected : NavigationRailLabelType.none;
} else {
return NavigationRailLabelType.all;
}
}
-Finder _opacityAboveLabel(String text) {
+Finder _visibilityAboveLabel(String text) {
return find.ancestor(
of: find.text(text),
- matching: find.byType(Opacity),
+ matching: find.byType(Visibility),
);
}
// Only valid when labelType != all.
-double _labelOpacity(WidgetTester tester, String text) {
- final Opacity opacityWidget = tester.widget<Opacity>(
+bool _labelVisibility(WidgetTester tester, String text) {
+ final Visibility visibilityWidget = tester.widget<Visibility>(
find.ancestor(
of: find.text(text),
- matching: find.byType(Opacity),
+ matching: find.byType(Visibility),
),
);
- return opacityWidget.opacity;
+ return visibilityWidget.visible;
}