Skip to content

Commit 23687c5

Browse files
authored
Add AnimationStyle to showBottomSheet and showModalBottomSheet (#145536)
fixes [Introduce animation customizable with `AnimationStyle` to `BottomSheet`](flutter/flutter#145532) ### Default bottom sheet animation ![00-ezgif com-video-to-gif-converter](https://github.com/flutter/flutter/assets/48603081/a295b002-b310-4dea-8bc4-23b1d299748c) ### Custom bottom sheet animation ![01-ezgif com-video-to-gif-converter](https://github.com/flutter/flutter/assets/48603081/8c5c3d5f-e67d-4ed5-880d-f17d262087e1) ### No bottom sheet animation ![02-ezgif com-video-to-gif-converter](https://github.com/flutter/flutter/assets/48603081/872409d8-8a8d-4db9-b95b-7f96a62cdffc)
1 parent 62adaff commit 23687c5

File tree

12 files changed

+1007
-6
lines changed

12 files changed

+1007
-6
lines changed
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/material.dart';
6+
7+
/// Flutter code sample for [showBottomSheet].
8+
9+
void main() => runApp(const BottomSheetExampleApp());
10+
11+
class BottomSheetExampleApp extends StatelessWidget {
12+
const BottomSheetExampleApp({super.key});
13+
14+
@override
15+
Widget build(BuildContext context) {
16+
return MaterialApp(
17+
home: Scaffold(
18+
appBar: AppBar(title: const Text('Bottom Sheet Sample')),
19+
body: const BottomSheetExample(),
20+
),
21+
);
22+
}
23+
}
24+
25+
enum AnimationStyles { defaultStyle, custom, none }
26+
const List<(AnimationStyles, String)> animationStyleSegments = <(AnimationStyles, String)>[
27+
(AnimationStyles.defaultStyle, 'Default'),
28+
(AnimationStyles.custom, 'Custom'),
29+
(AnimationStyles.none, 'None'),
30+
];
31+
32+
class BottomSheetExample extends StatefulWidget {
33+
const BottomSheetExample({super.key});
34+
35+
@override
36+
State<BottomSheetExample> createState() => _BottomSheetExampleState();
37+
}
38+
39+
class _BottomSheetExampleState extends State<BottomSheetExample> {
40+
Set<AnimationStyles> _animationStyleSelection = <AnimationStyles>{AnimationStyles.defaultStyle};
41+
AnimationStyle? _animationStyle;
42+
43+
@override
44+
Widget build(BuildContext context) {
45+
return Center(
46+
child: Column(
47+
mainAxisAlignment: MainAxisAlignment.center,
48+
children: <Widget>[
49+
SegmentedButton<AnimationStyles>(
50+
selected: _animationStyleSelection,
51+
onSelectionChanged: (Set<AnimationStyles> styles) {
52+
setState(() {
53+
_animationStyle = switch (styles.first) {
54+
AnimationStyles.defaultStyle => null,
55+
AnimationStyles.custom => AnimationStyle(
56+
duration: const Duration(seconds: 3),
57+
reverseDuration: const Duration(seconds: 1),
58+
),
59+
AnimationStyles.none => AnimationStyle.noAnimation,
60+
};
61+
_animationStyleSelection = styles;
62+
});
63+
},
64+
segments: animationStyleSegments
65+
.map<ButtonSegment<AnimationStyles>>(((AnimationStyles, String) shirt) {
66+
return ButtonSegment<AnimationStyles>(value: shirt.$1, label: Text(shirt.$2));
67+
})
68+
.toList(),
69+
),
70+
const SizedBox(height: 10),
71+
ElevatedButton(
72+
child: const Text('showBottomSheet'),
73+
onPressed: () {
74+
showBottomSheet(
75+
context: context,
76+
sheetAnimationStyle: _animationStyle,
77+
builder: (BuildContext context) {
78+
return SizedBox.expand(
79+
child: Center(
80+
child: Column(
81+
mainAxisAlignment: MainAxisAlignment.center,
82+
mainAxisSize: MainAxisSize.min,
83+
children: <Widget>[
84+
const Text('Bottom sheet'),
85+
ElevatedButton(
86+
child: const Text('Close'),
87+
onPressed: () => Navigator.pop(context),
88+
),
89+
],
90+
),
91+
),
92+
);
93+
},
94+
);
95+
},
96+
),
97+
],
98+
),
99+
);
100+
}
101+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/material.dart';
6+
7+
/// Flutter code sample for [showModalBottomSheet].
8+
9+
void main() => runApp(const ModalBottomSheetApp());
10+
11+
class ModalBottomSheetApp extends StatelessWidget {
12+
const ModalBottomSheetApp({super.key});
13+
14+
@override
15+
Widget build(BuildContext context) {
16+
return MaterialApp(
17+
home: Scaffold(
18+
appBar: AppBar(title: const Text('Modal Bottom Sheet Sample')),
19+
body: const ModalBottomSheetExample(),
20+
),
21+
);
22+
}
23+
}
24+
25+
enum AnimationStyles { defaultStyle, custom, none }
26+
const List<(AnimationStyles, String)> animationStyleSegments = <(AnimationStyles, String)>[
27+
(AnimationStyles.defaultStyle, 'Default'),
28+
(AnimationStyles.custom, 'Custom'),
29+
(AnimationStyles.none, 'None'),
30+
];
31+
32+
class ModalBottomSheetExample extends StatefulWidget {
33+
const ModalBottomSheetExample({super.key});
34+
35+
@override
36+
State<ModalBottomSheetExample> createState() => _ModalBottomSheetExampleState();
37+
}
38+
39+
class _ModalBottomSheetExampleState extends State<ModalBottomSheetExample> {
40+
Set<AnimationStyles> _animationStyleSelection = <AnimationStyles>{AnimationStyles.defaultStyle};
41+
AnimationStyle? _animationStyle;
42+
43+
@override
44+
Widget build(BuildContext context) {
45+
return Center(
46+
child: Column(
47+
mainAxisAlignment: MainAxisAlignment.center,
48+
children: <Widget>[
49+
SegmentedButton<AnimationStyles>(
50+
selected: _animationStyleSelection,
51+
onSelectionChanged: (Set<AnimationStyles> styles) {
52+
setState(() {
53+
_animationStyle = switch (styles.first) {
54+
AnimationStyles.defaultStyle => null,
55+
AnimationStyles.custom => AnimationStyle(
56+
duration: const Duration(seconds: 3),
57+
reverseDuration: const Duration(seconds: 1),
58+
),
59+
AnimationStyles.none => AnimationStyle.noAnimation,
60+
};
61+
_animationStyleSelection = styles;
62+
});
63+
},
64+
segments: animationStyleSegments
65+
.map<ButtonSegment<AnimationStyles>>(((AnimationStyles, String) shirt) {
66+
return ButtonSegment<AnimationStyles>(value: shirt.$1, label: Text(shirt.$2));
67+
})
68+
.toList(),
69+
),
70+
const SizedBox(height: 10),
71+
ElevatedButton(
72+
child: const Text('showModalBottomSheet'),
73+
onPressed: () {
74+
showModalBottomSheet<void>(
75+
context: context,
76+
sheetAnimationStyle: _animationStyle,
77+
builder: (BuildContext context) {
78+
return SizedBox.expand(
79+
child: Center(
80+
child: Column(
81+
mainAxisAlignment: MainAxisAlignment.center,
82+
mainAxisSize: MainAxisSize.min,
83+
children: <Widget>[
84+
const Text('Modal bottom sheet'),
85+
ElevatedButton(
86+
child: const Text('Close'),
87+
onPressed: () => Navigator.pop(context),
88+
),
89+
],
90+
),
91+
),
92+
);
93+
},
94+
);
95+
},
96+
),
97+
],
98+
),
99+
);
100+
}
101+
}

examples/api/lib/material/scaffold/scaffold_messenger_state.show_snack_bar.2.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class SnackBarExample extends StatefulWidget {
3434
}
3535

3636
class _SnackBarExampleState extends State<SnackBarExample> {
37-
final Set<AnimationStyles> _animationStyleSelection = <AnimationStyles>{AnimationStyles.defaultStyle};
37+
Set<AnimationStyles> _animationStyleSelection = <AnimationStyles>{AnimationStyles.defaultStyle};
3838
AnimationStyle? _animationStyle;
3939

4040
@override
@@ -57,6 +57,7 @@ class _SnackBarExampleState extends State<SnackBarExample> {
5757
),
5858
AnimationStyles.none => AnimationStyle.noAnimation,
5959
};
60+
_animationStyleSelection = styles;
6061
});
6162
},
6263
segments: animationStyleSegments
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/material.dart';
6+
7+
/// Flutter code sample for [ScaffoldState.showBottomSheet].
8+
9+
void main() => runApp(const ShowBottomSheetExampleApp());
10+
11+
class ShowBottomSheetExampleApp extends StatelessWidget {
12+
const ShowBottomSheetExampleApp({super.key});
13+
14+
@override
15+
Widget build(BuildContext context) {
16+
return MaterialApp(
17+
home: Scaffold(
18+
appBar: AppBar(title: const Text('ScaffoldState BottomSheet Sample')),
19+
body: const ShowBottomSheetExample(),
20+
),
21+
);
22+
}
23+
}
24+
25+
enum AnimationStyles { defaultStyle, custom, none }
26+
const List<(AnimationStyles, String)> animationStyleSegments = <(AnimationStyles, String)>[
27+
(AnimationStyles.defaultStyle, 'Default'),
28+
(AnimationStyles.custom, 'Custom'),
29+
(AnimationStyles.none, 'None'),
30+
];
31+
32+
class ShowBottomSheetExample extends StatefulWidget {
33+
const ShowBottomSheetExample({super.key});
34+
35+
@override
36+
State<ShowBottomSheetExample> createState() => _ShowBottomSheetExampleState();
37+
}
38+
39+
class _ShowBottomSheetExampleState extends State<ShowBottomSheetExample> {
40+
Set<AnimationStyles> _animationStyleSelection = <AnimationStyles>{AnimationStyles.defaultStyle};
41+
AnimationStyle? _animationStyle;
42+
43+
@override
44+
Widget build(BuildContext context) {
45+
return Center(
46+
child: Column(
47+
mainAxisAlignment: MainAxisAlignment.center,
48+
children: <Widget>[
49+
SegmentedButton<AnimationStyles>(
50+
selected: _animationStyleSelection,
51+
onSelectionChanged: (Set<AnimationStyles> styles) {
52+
setState(() {
53+
_animationStyle = switch (styles.first) {
54+
AnimationStyles.defaultStyle => null,
55+
AnimationStyles.custom => AnimationStyle(
56+
duration: const Duration(seconds: 3),
57+
reverseDuration: const Duration(seconds: 1),
58+
),
59+
AnimationStyles.none => AnimationStyle.noAnimation,
60+
};
61+
_animationStyleSelection = styles;
62+
});
63+
},
64+
segments: animationStyleSegments
65+
.map<ButtonSegment<AnimationStyles>>(((AnimationStyles, String) shirt) {
66+
return ButtonSegment<AnimationStyles>(value: shirt.$1, label: Text(shirt.$2));
67+
})
68+
.toList(),
69+
),
70+
const SizedBox(height: 10),
71+
ElevatedButton(
72+
child: const Text('showBottomSheet'),
73+
onPressed: () {
74+
Scaffold.of(context).showBottomSheet(
75+
sheetAnimationStyle: _animationStyle,
76+
(BuildContext context) {
77+
return SizedBox(
78+
height: 200,
79+
child: Center(
80+
child: Column(
81+
mainAxisAlignment: MainAxisAlignment.center,
82+
mainAxisSize: MainAxisSize.min,
83+
children: <Widget>[
84+
const Text('BottomSheet'),
85+
ElevatedButton(
86+
child: const Text('Close'),
87+
onPressed: () {
88+
Navigator.pop(context);
89+
},
90+
),
91+
],
92+
),
93+
),
94+
);
95+
},
96+
);
97+
},
98+
),
99+
],
100+
),
101+
);
102+
}
103+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/material.dart';
6+
import 'package:flutter_api_samples/material/bottom_sheet/show_bottom_sheet.0.dart' as example;
7+
import 'package:flutter_test/flutter_test.dart';
8+
9+
void main() {
10+
testWidgets('Bottom sheet animation can be customized using AnimationStyle', (WidgetTester tester) async {
11+
await tester.pumpWidget(
12+
const example.BottomSheetExampleApp(),
13+
);
14+
15+
// Show the bottom sheet with default animation style.
16+
await tester.tap(find.widgetWithText(ElevatedButton, 'showBottomSheet'));
17+
await tester.pump();
18+
// Advance the animation by 1/2 of the default forward duration.
19+
await tester.pump(const Duration(milliseconds: 125));
20+
21+
// The bottom sheet is partially visible.
22+
expect(tester.getTopLeft(find.byType(BottomSheet)).dy, closeTo(178.0, 0.1));
23+
24+
// Advance the animation by 1/2 of the default forward duration.
25+
await tester.pump(const Duration(milliseconds: 125));
26+
27+
// The bottom sheet is fully visible.
28+
expect(tester.getTopLeft(find.byType(BottomSheet)).dy, equals(56.0));
29+
30+
// Dismiss the bottom sheet.
31+
await tester.tap(find.widgetWithText(ElevatedButton, 'Close'));
32+
await tester.pumpAndSettle();
33+
34+
// Select custom animation style.
35+
await tester.tap(find.text('Custom'));
36+
await tester.pumpAndSettle();
37+
38+
// Show the bottom sheet with custom animation style.
39+
await tester.tap(find.widgetWithText(ElevatedButton, 'showBottomSheet'));
40+
await tester.pump();
41+
// Advance the animation by 1/2 of the custom forward duration.
42+
await tester.pump(const Duration(milliseconds: 1500));
43+
44+
// The bottom sheet is partially visible.
45+
expect(tester.getTopLeft(find.byType(BottomSheet)).dy, closeTo(178.0, 0.1));
46+
47+
// Advance the animation by 1/2 of the custom forward duration.
48+
await tester.pump(const Duration(milliseconds: 1500));
49+
50+
// The bottom sheet is fully visible.
51+
expect(tester.getTopLeft(find.byType(BottomSheet)).dy, equals(56.0));
52+
53+
// Dismiss the bottom sheet.
54+
await tester.tap(find.widgetWithText(ElevatedButton, 'Close'));
55+
await tester.pumpAndSettle();
56+
57+
// Select no animation style.
58+
await tester.tap(find.text('None'));
59+
await tester.pumpAndSettle();
60+
61+
// Show the bottom sheet with no animation style.
62+
await tester.tap(find.widgetWithText(ElevatedButton, 'showBottomSheet'));
63+
await tester.pump();
64+
expect(tester.getTopLeft(find.byType(BottomSheet)).dy, equals(56.0));
65+
});
66+
}

0 commit comments

Comments
 (0)