From 6a5d32962c1fc19c837754c5d4e227de6574a6f9 Mon Sep 17 00:00:00 2001 From: rami-a <2364772+rami-a@users.noreply.github.com> Date: Wed, 21 Aug 2019 16:14:52 -0400 Subject: [PATCH] [Material] Add clip property to bottom sheet and theme (#38831) --- .../lib/src/material/bottom_sheet.dart | 26 +++++++++++++++++++ .../lib/src/material/bottom_sheet_theme.dart | 14 +++++++++- .../flutter/lib/src/material/scaffold.dart | 7 +++++ .../test/material/bottom_sheet_test.dart | 3 +++ .../material/bottom_sheet_theme_test.dart | 9 +++++++ .../persistent_bottom_sheet_test.dart | 4 ++- 6 files changed, 61 insertions(+), 2 deletions(-) diff --git a/packages/flutter/lib/src/material/bottom_sheet.dart b/packages/flutter/lib/src/material/bottom_sheet.dart index d947052390..ec7cede960 100644 --- a/packages/flutter/lib/src/material/bottom_sheet.dart +++ b/packages/flutter/lib/src/material/bottom_sheet.dart @@ -60,6 +60,7 @@ class BottomSheet extends StatefulWidget { this.backgroundColor, this.elevation, this.shape, + this.clipBehavior, @required this.onClosing, @required this.builder, }) : assert(enableDrag != null), @@ -115,6 +116,19 @@ class BottomSheet extends StatefulWidget { /// 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 @@ class _BottomSheetState extends State { 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( onNotification: extentChanged, child: widget.builder(context), @@ -247,6 +263,7 @@ class _ModalBottomSheet extends StatefulWidget { this.backgroundColor, this.elevation, this.shape, + this.clipBehavior, this.isScrollControlled = false, }) : assert(isScrollControlled != null), super(key: key); @@ -256,6 +273,7 @@ class _ModalBottomSheet extends StatefulWidget { final Color backgroundColor; final double elevation; final ShapeBorder shape; + final Clip clipBehavior; @override _ModalBottomSheetState createState() => _ModalBottomSheetState(); @@ -306,6 +324,7 @@ class _ModalBottomSheetState extends State<_ModalBottomSheet> { backgroundColor: widget.backgroundColor, elevation: widget.elevation, shape: widget.shape, + clipBehavior: widget.clipBehavior, ), ), ), @@ -323,6 +342,7 @@ class _ModalBottomSheetRoute extends PopupRoute { this.backgroundColor, this.elevation, this.shape, + this.clipBehavior, @required this.isScrollControlled, RouteSettings settings, }) : assert(isScrollControlled != null), @@ -334,6 +354,7 @@ class _ModalBottomSheetRoute extends PopupRoute { final Color backgroundColor; final double elevation; final ShapeBorder shape; + final Clip clipBehavior; @override Duration get transitionDuration => _bottomSheetDuration; @@ -369,6 +390,7 @@ class _ModalBottomSheetRoute extends PopupRoute { backgroundColor: backgroundColor, elevation: elevation, shape: shape, + clipBehavior: clipBehavior, isScrollControlled: isScrollControlled ), ); @@ -423,6 +445,7 @@ Future showModalBottomSheet({ Color backgroundColor, double elevation, ShapeBorder shape, + Clip clipBehavior, bool isScrollControlled = false, bool useRootNavigator = false, }) { @@ -441,6 +464,7 @@ Future showModalBottomSheet({ backgroundColor: backgroundColor, elevation: elevation, shape: shape, + clipBehavior: clipBehavior, )); } @@ -485,6 +509,7 @@ PersistentBottomSheetController showBottomSheet({ Color backgroundColor, double elevation, ShapeBorder shape, + Clip clipBehavior, }) { assert(context != null); assert(builder != null); @@ -495,5 +520,6 @@ PersistentBottomSheetController showBottomSheet({ 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 23d121ff5b..fcf0fd7500 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 @@ class BottomSheetThemeData extends Diagnosticable { this.backgroundColor, this.elevation, this.shape, + this.clipBehavior, }); /// Default value for [BottomSheet.backgroundColor]. @@ -50,17 +51,24 @@ class BottomSheetThemeData extends Diagnosticable { /// [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 @@ class BottomSheetThemeData extends Diagnosticable { 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 @@ class BottomSheetThemeData extends Diagnosticable { backgroundColor, elevation, shape, + clipBehavior, ); } @@ -98,7 +108,8 @@ class BottomSheetThemeData extends Diagnosticable { 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 @@ class BottomSheetThemeData extends Diagnosticable { properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: null)); properties.add(DoubleProperty('elevation', elevation, defaultValue: null)); properties.add(DiagnosticsProperty('shape', shape, defaultValue: null)); + properties.add(DiagnosticsProperty('clipBehavior', clipBehavior, defaultValue: null)); } } diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart index 4488d8e8ef..11e6b4d1b4 100644 --- a/packages/flutter/lib/src/material/scaffold.dart +++ b/packages/flutter/lib/src/material/scaffold.dart @@ -1622,6 +1622,7 @@ class ScaffoldState extends State with TickerProviderStateMixin { Color backgroundColor, double elevation, ShapeBorder shape, + Clip clipBehavior, }) { assert(() { if (widget.bottomSheet != null && isPersistent && _currentBottomSheet != null) { @@ -1702,6 +1703,7 @@ class ScaffoldState extends State with TickerProviderStateMixin { backgroundColor: backgroundColor, elevation: elevation, shape: shape, + clipBehavior: clipBehavior, ); if (!isPersistent) @@ -1759,6 +1761,7 @@ class ScaffoldState extends State with TickerProviderStateMixin { Color backgroundColor, double elevation, ShapeBorder shape, + Clip clipBehavior, }) { assert(() { if (widget.bottomSheet != null) { @@ -1782,6 +1785,7 @@ class ScaffoldState extends State with TickerProviderStateMixin { backgroundColor: backgroundColor, elevation: elevation, shape: shape, + clipBehavior: clipBehavior, ); }); return _currentBottomSheet; @@ -2313,6 +2317,7 @@ class _StandardBottomSheet extends StatefulWidget { 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 @@ class _StandardBottomSheet extends StatefulWidget { final Color backgroundColor; final double elevation; final ShapeBorder shape; + final Clip clipBehavior; @override _StandardBottomSheetState createState() => _StandardBottomSheetState(); @@ -2412,6 +2418,7 @@ class _StandardBottomSheetState extends State<_StandardBottomSheet> { 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 f0cf7e6a05..262d28a27a 100644 --- a/packages/flutter/test/material/bottom_sheet_test.dart +++ b/packages/flutter/test/material/bottom_sheet_test.dart @@ -270,6 +270,7 @@ void main() { 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 @@ void main() { backgroundColor: color, elevation: elevation, shape: shape, + clipBehavior: clipBehavior, builder: (BuildContext context) { return Container( child: const Text('BottomSheet'), @@ -297,6 +299,7 @@ void main() { 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 0d19a67931..bcd7f38b38 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 @@ void main() { 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 @@ void main() { backgroundColor: const Color(0xFFFFFFFF), elevation: 2.0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(2.0)), + clipBehavior: Clip.antiAlias, ).debugFillProperties(builder); final List description = builder.properties @@ -48,6 +50,7 @@ void main() { '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 @@ void main() { 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 @@ void main() { 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 @@ void main() { 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 @@ void main() { backgroundColor: backgroundColor, elevation: elevation, shape: shape, + clipBehavior: Clip.hardEdge, onClosing: () {}, builder: (BuildContext context) { return Container(); @@ -131,6 +138,7 @@ void main() { expect(material.color, backgroundColor); expect(material.elevation, elevation); expect(material.shape, shape); + expect(material.clipBehavior, clipBehavior); }); } @@ -139,5 +147,6 @@ BottomSheetThemeData _bottomSheetTheme() { 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 f3f0fd1d32..bf22709fdd 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 @@ void main() { 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 @@ void main() { 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 @@ void main() { 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 {