[Material] Add clip property to bottom sheet and theme (#38831)

diff --git a/packages/flutter/lib/src/material/bottom_sheet.dart b/packages/flutter/lib/src/material/bottom_sheet.dart
index d947052..ec7cede 100644
--- a/packages/flutter/lib/src/material/bottom_sheet.dart
+++ b/packages/flutter/lib/src/material/bottom_sheet.dart
@@ -60,6 +60,7 @@
     this.backgroundColor,
     this.elevation,
     this.shape,
+    this.clipBehavior,
     @required this.onClosing,
     @required this.builder,
   }) : assert(enableDrag != null),
@@ -115,6 +116,19 @@
   /// Defaults to null and falls back to [Material]'s default.
   final ShapeBorder shape;
 
+  /// {@macro flutter.widgets.Clip}
+  ///
+  /// Defines the bottom sheet's [Material.clipBehavior].
+  ///
+  /// Use this property to enable clipping of content when the bottom sheet has
+  /// a custom [shape] and the content can extend past this shape. For example,
+  /// a bottom sheet with rounded corners and an edge-to-edge [Image] at the
+  /// top.
+  ///
+  /// If this property is null then [ThemeData.bottomSheetTheme.clipBehavior] is
+  /// used. If that's null then the behavior will be [Clip.none].
+  final Clip clipBehavior;
+
   @override
   _BottomSheetState createState() => _BottomSheetState();
 
@@ -185,12 +199,14 @@
     final Color color = widget.backgroundColor ?? bottomSheetTheme.backgroundColor;
     final double elevation = widget.elevation ?? bottomSheetTheme.elevation ?? 0;
     final ShapeBorder shape = widget.shape ?? bottomSheetTheme.shape;
+    final Clip clipBehavior = widget.clipBehavior ?? bottomSheetTheme.clipBehavior ?? Clip.none;
 
     final Widget bottomSheet = Material(
       key: _childKey,
       color: color,
       elevation: elevation,
       shape: shape,
+      clipBehavior: clipBehavior,
       child: NotificationListener<DraggableScrollableNotification>(
         onNotification: extentChanged,
         child: widget.builder(context),
@@ -247,6 +263,7 @@
     this.backgroundColor,
     this.elevation,
     this.shape,
+    this.clipBehavior,
     this.isScrollControlled = false,
   }) : assert(isScrollControlled != null),
        super(key: key);
@@ -256,6 +273,7 @@
   final Color backgroundColor;
   final double elevation;
   final ShapeBorder shape;
+  final Clip clipBehavior;
 
   @override
   _ModalBottomSheetState<T> createState() => _ModalBottomSheetState<T>();
@@ -306,6 +324,7 @@
                 backgroundColor: widget.backgroundColor,
                 elevation: widget.elevation,
                 shape: widget.shape,
+                clipBehavior: widget.clipBehavior,
               ),
             ),
           ),
@@ -323,6 +342,7 @@
     this.backgroundColor,
     this.elevation,
     this.shape,
+    this.clipBehavior,
     @required this.isScrollControlled,
     RouteSettings settings,
   }) : assert(isScrollControlled != null),
@@ -334,6 +354,7 @@
   final Color backgroundColor;
   final double elevation;
   final ShapeBorder shape;
+  final Clip clipBehavior;
 
   @override
   Duration get transitionDuration => _bottomSheetDuration;
@@ -369,6 +390,7 @@
         backgroundColor: backgroundColor,
         elevation: elevation,
         shape: shape,
+        clipBehavior: clipBehavior,
         isScrollControlled: isScrollControlled
       ),
     );
@@ -423,6 +445,7 @@
   Color backgroundColor,
   double elevation,
   ShapeBorder shape,
+  Clip clipBehavior,
   bool isScrollControlled = false,
   bool useRootNavigator = false,
 }) {
@@ -441,6 +464,7 @@
     backgroundColor: backgroundColor,
     elevation: elevation,
     shape: shape,
+    clipBehavior: clipBehavior,
   ));
 }
 
@@ -485,6 +509,7 @@
   Color backgroundColor,
   double elevation,
   ShapeBorder shape,
+  Clip clipBehavior,
 }) {
   assert(context != null);
   assert(builder != null);
@@ -495,5 +520,6 @@
     backgroundColor: backgroundColor,
     elevation: elevation,
     shape: shape,
+    clipBehavior: clipBehavior,
   );
 }
diff --git a/packages/flutter/lib/src/material/bottom_sheet_theme.dart b/packages/flutter/lib/src/material/bottom_sheet_theme.dart
index 23d121f..fcf0fd7 100644
--- a/packages/flutter/lib/src/material/bottom_sheet_theme.dart
+++ b/packages/flutter/lib/src/material/bottom_sheet_theme.dart
@@ -30,6 +30,7 @@
     this.backgroundColor,
     this.elevation,
     this.shape,
+    this.clipBehavior,
   });
 
   /// Default value for [BottomSheet.backgroundColor].
@@ -50,17 +51,24 @@
   /// [BottomSheet] is rectangular.
   final ShapeBorder shape;
 
+  /// Default value for [BottomSheet.clipBehavior].
+  ///
+  /// If null, [BottomSheet] uses [Clip.none].
+  final Clip clipBehavior;
+
   /// Creates a copy of this object with the given fields replaced with the
   /// new values.
   BottomSheetThemeData copyWith({
     Color backgroundColor,
     double elevation,
     ShapeBorder shape,
+    Clip clipBehavior,
   }) {
     return BottomSheetThemeData(
       backgroundColor: backgroundColor ?? this.backgroundColor,
       elevation: elevation ?? this.elevation,
       shape: shape ?? this.shape,
+      clipBehavior: clipBehavior ?? this.clipBehavior,
     );
   }
 
@@ -77,6 +85,7 @@
       backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t),
       elevation: lerpDouble(a?.elevation, b?.elevation, t),
       shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
+      clipBehavior: t < 0.5 ? a?.clipBehavior : b?.clipBehavior,
     );
   }
 
@@ -86,6 +95,7 @@
       backgroundColor,
       elevation,
       shape,
+      clipBehavior,
     );
   }
 
@@ -98,7 +108,8 @@
     final BottomSheetThemeData typedOther = other;
     return typedOther.backgroundColor == backgroundColor
         && typedOther.elevation == elevation
-        && typedOther.shape == shape;
+        && typedOther.shape == shape
+        && typedOther.clipBehavior == clipBehavior;
   }
 
   @override
@@ -107,5 +118,6 @@
     properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: null));
     properties.add(DoubleProperty('elevation', elevation, defaultValue: null));
     properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
+    properties.add(DiagnosticsProperty<Clip>('clipBehavior', clipBehavior, defaultValue: null));
   }
 }
diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart
index 4488d8e..11e6b4d 100644
--- a/packages/flutter/lib/src/material/scaffold.dart
+++ b/packages/flutter/lib/src/material/scaffold.dart
@@ -1622,6 +1622,7 @@
     Color backgroundColor,
     double elevation,
     ShapeBorder shape,
+    Clip clipBehavior,
   }) {
     assert(() {
       if (widget.bottomSheet != null && isPersistent && _currentBottomSheet != null) {
@@ -1702,6 +1703,7 @@
       backgroundColor: backgroundColor,
       elevation: elevation,
       shape: shape,
+      clipBehavior: clipBehavior,
     );
 
     if (!isPersistent)
@@ -1759,6 +1761,7 @@
     Color backgroundColor,
     double elevation,
     ShapeBorder shape,
+    Clip clipBehavior,
   }) {
     assert(() {
       if (widget.bottomSheet != null) {
@@ -1782,6 +1785,7 @@
         backgroundColor: backgroundColor,
         elevation: elevation,
         shape: shape,
+        clipBehavior: clipBehavior,
       );
     });
     return _currentBottomSheet;
@@ -2313,6 +2317,7 @@
     this.backgroundColor,
     this.elevation,
     this.shape,
+    this.clipBehavior,
   }) : super(key: key);
 
   final AnimationController animationController; // we control it, but it must be disposed by whoever created it.
@@ -2324,6 +2329,7 @@
   final Color backgroundColor;
   final double elevation;
   final ShapeBorder shape;
+  final Clip clipBehavior;
 
   @override
   _StandardBottomSheetState createState() => _StandardBottomSheetState();
@@ -2412,6 +2418,7 @@
             backgroundColor: widget.backgroundColor,
             elevation: widget.elevation,
             shape: widget.shape,
+            clipBehavior: widget.clipBehavior,
           ),
         ),
       );
diff --git a/packages/flutter/test/material/bottom_sheet_test.dart b/packages/flutter/test/material/bottom_sheet_test.dart
index f0cf7e6..262d28a 100644
--- a/packages/flutter/test/material/bottom_sheet_test.dart
+++ b/packages/flutter/test/material/bottom_sheet_test.dart
@@ -270,6 +270,7 @@
     const Color color = Colors.pink;
     const double elevation = 9.0;
     final ShapeBorder shape = BeveledRectangleBorder(borderRadius: BorderRadius.circular(12));
+    const Clip clipBehavior = Clip.antiAlias;
 
     await tester.pumpWidget(MaterialApp(
       home: Scaffold(
@@ -283,6 +284,7 @@
       backgroundColor: color,
       elevation: elevation,
       shape: shape,
+      clipBehavior: clipBehavior,
       builder: (BuildContext context) {
         return Container(
           child: const Text('BottomSheet'),
@@ -297,6 +299,7 @@
     expect(bottomSheet.backgroundColor, color);
     expect(bottomSheet.elevation, elevation);
     expect(bottomSheet.shape, shape);
+    expect(bottomSheet.clipBehavior, clipBehavior);
   });
 
   testWidgets('modal BottomSheet with scrollController has semantics', (WidgetTester tester) async {
diff --git a/packages/flutter/test/material/bottom_sheet_theme_test.dart b/packages/flutter/test/material/bottom_sheet_theme_test.dart
index 0d19a67..bcd7f38 100644
--- a/packages/flutter/test/material/bottom_sheet_theme_test.dart
+++ b/packages/flutter/test/material/bottom_sheet_theme_test.dart
@@ -17,6 +17,7 @@
     expect(bottomSheetTheme.backgroundColor, null);
     expect(bottomSheetTheme.elevation, null);
     expect(bottomSheetTheme.shape, null);
+    expect(bottomSheetTheme.clipBehavior, null);
   });
 
   testWidgets('Default BottomSheetThemeData debugFillProperties', (WidgetTester tester) async {
@@ -37,6 +38,7 @@
       backgroundColor: const Color(0xFFFFFFFF),
       elevation: 2.0,
       shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(2.0)),
+      clipBehavior: Clip.antiAlias,
     ).debugFillProperties(builder);
 
     final List<String> description = builder.properties
@@ -48,6 +50,7 @@
       'backgroundColor: Color(0xffffffff)',
       'elevation: 2.0',
       'shape: RoundedRectangleBorder(BorderSide(Color(0xff000000), 0.0, BorderStyle.none), BorderRadius.circular(2.0))',
+      'clipBehavior: Clip.antiAlias'
     ]);
   });
 
@@ -72,6 +75,7 @@
     expect(material.color, null);
     expect(material.elevation, 0.0);
     expect(material.shape, null);
+    expect(material.clipBehavior, Clip.none);
   });
 
   testWidgets('BottomSheet uses values from BottomSheetThemeData', (WidgetTester tester) async {
@@ -98,6 +102,7 @@
     expect(material.color, bottomSheetTheme.backgroundColor);
     expect(material.elevation, bottomSheetTheme.elevation);
     expect(material.shape, bottomSheetTheme.shape);
+    expect(material.clipBehavior, bottomSheetTheme.clipBehavior);
   });
 
   testWidgets('BottomSheet widget properties take priority over theme', (WidgetTester tester) async {
@@ -106,6 +111,7 @@
     const ShapeBorder shape = RoundedRectangleBorder(
       borderRadius: BorderRadius.all(Radius.circular(9.0)),
     );
+    const Clip clipBehavior = Clip.hardEdge;
 
     await tester.pumpWidget(MaterialApp(
       theme: ThemeData(bottomSheetTheme: _bottomSheetTheme()),
@@ -114,6 +120,7 @@
           backgroundColor: backgroundColor,
           elevation: elevation,
           shape: shape,
+          clipBehavior: Clip.hardEdge,
           onClosing: () {},
           builder: (BuildContext context) {
             return Container();
@@ -131,6 +138,7 @@
     expect(material.color, backgroundColor);
     expect(material.elevation, elevation);
     expect(material.shape, shape);
+    expect(material.clipBehavior, clipBehavior);
   });
 }
 
@@ -139,5 +147,6 @@
     backgroundColor: Colors.orange,
     elevation: 12.0,
     shape: BeveledRectangleBorder(borderRadius: BorderRadius.circular(12)),
+    clipBehavior: Clip.antiAlias,
   );
 }
diff --git a/packages/flutter/test/material/persistent_bottom_sheet_test.dart b/packages/flutter/test/material/persistent_bottom_sheet_test.dart
index f3f0fd1..bf22709 100644
--- a/packages/flutter/test/material/persistent_bottom_sheet_test.dart
+++ b/packages/flutter/test/material/persistent_bottom_sheet_test.dart
@@ -431,6 +431,7 @@
     const Color color = Colors.pink;
     const double elevation = 9.0;
     final ShapeBorder shape = BeveledRectangleBorder(borderRadius: BorderRadius.circular(12));
+    const Clip clipBehavior = Clip.antiAlias;
 
     await tester.pumpWidget(MaterialApp(
       home: Scaffold(
@@ -449,7 +450,7 @@
           Container(height: 100.0, child: const Text('Three')),
         ],
       );
-    }, backgroundColor: color, elevation: elevation, shape: shape);
+    }, backgroundColor: color, elevation: elevation, shape: shape, clipBehavior: clipBehavior);
 
     await tester.pumpAndSettle();
 
@@ -457,6 +458,7 @@
     expect(bottomSheet.backgroundColor, color);
     expect(bottomSheet.elevation, elevation);
     expect(bottomSheet.shape, shape);
+    expect(bottomSheet.clipBehavior, clipBehavior);
   });
 
   testWidgets('PersistentBottomSheetController.close dismisses the bottom sheet', (WidgetTester tester) async {