[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;
 }