Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit 6a66aa2

Browse files
authored
Add Material 3 support for BottomAppBar (reland #106525) (#114439)
* Revert "Revert "Add Material 3 support for BottomAppBar" (#114421)" This reverts commit 210a2aa. * Regenerated the defaults from tokens and fixed tests. * Fixed the tests. * Updated the shape token template to optimize the a common case.
1 parent 1a150ff commit 6a66aa2

File tree

10 files changed

+653
-110
lines changed

10 files changed

+653
-110
lines changed

dev/tools/gen_defaults/bin/gen_defaults.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import 'dart:io';
2020
import 'package:gen_defaults/action_chip_template.dart';
2121
import 'package:gen_defaults/app_bar_template.dart';
2222
import 'package:gen_defaults/banner_template.dart';
23+
import 'package:gen_defaults/bottom_app_bar_template.dart';
2324
import 'package:gen_defaults/bottom_sheet_template.dart';
2425
import 'package:gen_defaults/button_template.dart';
2526
import 'package:gen_defaults/card_template.dart';
@@ -121,6 +122,7 @@ Future<void> main(List<String> args) async {
121122
ActionChipTemplate('ActionChip', '$materialLib/action_chip.dart', tokens).updateFile();
122123
AppBarTemplate('AppBar', '$materialLib/app_bar.dart', tokens).updateFile();
123124
BannerTemplate('Banner', '$materialLib/banner.dart', tokens).updateFile();
125+
BottomAppBarTemplate('BottomAppBar', '$materialLib/bottom_app_bar.dart', tokens).updateFile();
124126
BottomSheetTemplate('BottomSheet', '$materialLib/bottom_sheet.dart', tokens).updateFile();
125127
ButtonTemplate('md.comp.elevated-button', 'ElevatedButton', '$materialLib/elevated_button.dart', tokens).updateFile();
126128
ButtonTemplate('md.comp.filled-button', 'FilledButton', '$materialLib/filled_button.dart', tokens).updateFile();
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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 'template.dart';
6+
7+
class BottomAppBarTemplate extends TokenTemplate {
8+
const BottomAppBarTemplate(super.blockName, super.fileName, super.tokens);
9+
10+
@override
11+
String generate() => '''
12+
// Generated version ${tokens["version"]}
13+
class _${blockName}DefaultsM3 extends BottomAppBarTheme {
14+
const _${blockName}DefaultsM3(this.context)
15+
: super(
16+
elevation: ${elevation('md.comp.bottom-app-bar.container')},
17+
height: ${tokens['md.comp.bottom-app-bar.container.height']},
18+
shape: const AutomaticNotchedShape(${shape('md.comp.bottom-app-bar.container', '')}),
19+
);
20+
21+
final BuildContext context;
22+
23+
@override
24+
Color? get color => ${componentColor('md.comp.bottom-app-bar.container')};
25+
26+
@override
27+
Color? get surfaceTintColor => ${componentColor('md.comp.bottom-app-bar.container.surface-tint-layer')};
28+
}
29+
''';
30+
}

dev/tools/gen_defaults/lib/template.dart

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -175,22 +175,25 @@ abstract class TokenTemplate {
175175
final double bottomLeft = shape['bottomLeft'] as double;
176176
final double bottomRight = shape['bottomRight'] as double;
177177
if (topLeft == topRight && topLeft == bottomLeft && topLeft == bottomRight) {
178+
if (topLeft == 0) {
179+
return '${prefix}RoundedRectangleBorder()';
180+
}
178181
return '${prefix}RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular($topLeft)))';
179182
}
180183
if (topLeft == topRight && bottomLeft == bottomRight) {
181184
return '${prefix}RoundedRectangleBorder(borderRadius: BorderRadius.vertical('
182-
'${topLeft > 0 ? 'top: Radius.circular($topLeft)':''}'
183-
'${topLeft > 0 && bottomLeft > 0 ? ',':''}'
184-
'${bottomLeft > 0 ? 'bottom: Radius.circular($bottomLeft)':''}'
185-
'))';
185+
'${topLeft > 0 ? 'top: Radius.circular($topLeft)':''}'
186+
'${topLeft > 0 && bottomLeft > 0 ? ',':''}'
187+
'${bottomLeft > 0 ? 'bottom: Radius.circular($bottomLeft)':''}'
188+
'))';
186189
}
187190
return '${prefix}RoundedRectangleBorder(borderRadius: '
188-
'BorderRadius.only('
189-
'topLeft: Radius.circular(${shape['topLeft']}), '
190-
'topRight: Radius.circular(${shape['topRight']}), '
191-
'bottomLeft: Radius.circular(${shape['bottomLeft']}), '
192-
'bottomRight: Radius.circular(${shape['bottomRight']})))';
193-
case 'SHAPE_FAMILY_CIRCULAR':
191+
'BorderRadius.only('
192+
'topLeft: Radius.circular(${shape['topLeft']}), '
193+
'topRight: Radius.circular(${shape['topRight']}), '
194+
'bottomLeft: Radius.circular(${shape['bottomLeft']}), '
195+
'bottomRight: Radius.circular(${shape['bottomRight']})))';
196+
case 'SHAPE_FAMILY_CIRCULAR':
194197
return '${prefix}StadiumBorder()';
195198
}
196199
print('Unsupported shape family type: ${shape['family']} for $componentToken');
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
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+
// Flutter code sample for BottomAppBar with Material 3
6+
7+
import 'package:flutter/material.dart';
8+
import 'package:flutter/rendering.dart';
9+
10+
void main() {
11+
runApp(const BottomAppBarDemo());
12+
}
13+
14+
class BottomAppBarDemo extends StatefulWidget {
15+
const BottomAppBarDemo({super.key});
16+
17+
@override
18+
State createState() => _BottomAppBarDemoState();
19+
}
20+
21+
class _BottomAppBarDemoState extends State<BottomAppBarDemo> {
22+
static const List<Color> colors = <Color>[
23+
Colors.yellow,
24+
Colors.orange,
25+
Colors.pink,
26+
Colors.purple,
27+
Colors.cyan,
28+
];
29+
30+
static final List<Widget> items = List<Widget>.generate(
31+
colors.length,
32+
(int index) => Container(color: colors[index], height: 150.0),
33+
).reversed.toList();
34+
35+
late ScrollController _controller;
36+
bool _showFab = true;
37+
bool _isElevated = true;
38+
bool _isVisible = true;
39+
40+
FloatingActionButtonLocation get _fabLocation => _isVisible
41+
? FloatingActionButtonLocation.endContained
42+
: FloatingActionButtonLocation.endFloat;
43+
44+
void _listen() {
45+
final ScrollDirection direction = _controller.position.userScrollDirection;
46+
if (direction == ScrollDirection.forward) {
47+
_show();
48+
} else if (direction == ScrollDirection.reverse) {
49+
_hide();
50+
}
51+
}
52+
53+
void _show() {
54+
if (!_isVisible) {
55+
setState(() => _isVisible = true);
56+
}
57+
}
58+
59+
void _hide() {
60+
if (_isVisible) {
61+
setState(() => _isVisible = false);
62+
}
63+
}
64+
65+
void _onShowFabChanged(bool value) {
66+
setState(() {
67+
_showFab = value;
68+
});
69+
}
70+
71+
void _onElevatedChanged(bool value) {
72+
setState(() {
73+
_isElevated = value;
74+
});
75+
}
76+
77+
void _addNewItem() {
78+
setState(() {
79+
items.insert(
80+
0,
81+
Container(color: colors[items.length % 5], height: 150.0),
82+
);
83+
});
84+
}
85+
86+
@override
87+
void initState() {
88+
super.initState();
89+
_controller = ScrollController();
90+
_controller.addListener(_listen);
91+
}
92+
93+
@override
94+
void dispose() {
95+
_controller.removeListener(_listen);
96+
_controller.dispose();
97+
super.dispose();
98+
}
99+
100+
@override
101+
Widget build(BuildContext context) {
102+
return MaterialApp(
103+
theme: ThemeData(useMaterial3: true),
104+
home: Scaffold(
105+
appBar: AppBar(
106+
title: const Text('Bottom App Bar Demo'),
107+
),
108+
body: Column(
109+
children: <Widget>[
110+
SwitchListTile(
111+
title: const Text('Floating Action Button'),
112+
value: _showFab,
113+
onChanged: _onShowFabChanged,
114+
),
115+
SwitchListTile(
116+
title: const Text('Bottom App Bar Elevation'),
117+
value: _isElevated,
118+
onChanged: _onElevatedChanged,
119+
),
120+
Expanded(
121+
child: ListView(
122+
controller: _controller,
123+
children: items.toList(),
124+
),
125+
),
126+
],
127+
),
128+
floatingActionButton: _showFab
129+
? FloatingActionButton(
130+
onPressed: _addNewItem,
131+
tooltip: 'Add New Item',
132+
elevation: _isVisible ? 0.0 : null,
133+
child: const Icon(Icons.add),
134+
)
135+
: null,
136+
floatingActionButtonLocation: _fabLocation,
137+
bottomNavigationBar: _DemoBottomAppBar(isElevated: _isElevated, isVisible: _isVisible),
138+
),
139+
);
140+
}
141+
}
142+
143+
class _DemoBottomAppBar extends StatelessWidget {
144+
const _DemoBottomAppBar({
145+
required this.isElevated,
146+
required this.isVisible,
147+
});
148+
149+
final bool isElevated;
150+
final bool isVisible;
151+
152+
@override
153+
Widget build(BuildContext context) {
154+
return AnimatedContainer(
155+
duration: const Duration(milliseconds: 200),
156+
height: isVisible ? 80.0 : 0,
157+
child: BottomAppBar(
158+
elevation: isElevated ? null : 0.0,
159+
child: Row(
160+
children: <Widget>[
161+
IconButton(
162+
tooltip: 'Open popup menu',
163+
icon: const Icon(Icons.more_vert),
164+
onPressed: () {
165+
final SnackBar snackBar = SnackBar(
166+
content: const Text('Yay! A SnackBar!'),
167+
action: SnackBarAction(
168+
label: 'Undo',
169+
onPressed: () {},
170+
),
171+
);
172+
173+
// Find the ScaffoldMessenger in the widget tree
174+
// and use it to show a SnackBar.
175+
ScaffoldMessenger.of(context).showSnackBar(snackBar);
176+
},
177+
),
178+
IconButton(
179+
tooltip: 'Search',
180+
icon: const Icon(Icons.search),
181+
onPressed: () {},
182+
),
183+
IconButton(
184+
tooltip: 'Favorite',
185+
icon: const Icon(Icons.favorite),
186+
onPressed: () {},
187+
),
188+
],
189+
),
190+
),
191+
);
192+
}
193+
}

0 commit comments

Comments
 (0)