Skip to content

Commit 66a84d1

Browse files
authored
Migrate IconButton to Material 3 - Part 1 (#105176)
* Added standard IconButton for M3 with new ButtonStyle field * Added IconButton examples for standard, filled, filled_tonal, and outlined types Co-authored-by: Qun Cheng <[email protected]>
1 parent 1718519 commit 66a84d1

File tree

5 files changed

+1292
-178
lines changed

5 files changed

+1292
-178
lines changed

dev/tools/gen_defaults/bin/gen_defaults.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import 'package:gen_defaults/button_template.dart';
2222
import 'package:gen_defaults/card_template.dart';
2323
import 'package:gen_defaults/dialog_template.dart';
2424
import 'package:gen_defaults/fab_template.dart';
25+
import 'package:gen_defaults/icon_button_template.dart';
2526
import 'package:gen_defaults/navigation_bar_template.dart';
2627
import 'package:gen_defaults/navigation_rail_template.dart';
2728
import 'package:gen_defaults/surface_tint.dart';
@@ -55,6 +56,9 @@ Future<void> main(List<String> args) async {
5556
'fab_large_primary.json',
5657
'fab_primary.json',
5758
'fab_small_primary.json',
59+
'icon_button.json',
60+
'icon_button_filled.json',
61+
'icon_button_filled_tonal.json',
5862
'motion.json',
5963
'navigation_bar.json',
6064
'navigation_rail.json',
@@ -86,6 +90,7 @@ Future<void> main(List<String> args) async {
8690
CardTemplate('$materialLib/card.dart', tokens).updateFile();
8791
DialogTemplate('$materialLib/dialog.dart', tokens).updateFile();
8892
FABTemplate('$materialLib/floating_action_button.dart', tokens).updateFile();
93+
IconButtonTemplate('$materialLib/icon_button.dart', tokens).updateFile();
8994
NavigationBarTemplate('$materialLib/navigation_bar.dart', tokens).updateFile();
9095
NavigationRailTemplate('$materialLib/navigation_rail.dart', tokens).updateFile();
9196
SurfaceTintTemplate('$materialLib/elevation_overlay.dart', tokens).updateFile();
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
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 IconButtonTemplate extends TokenTemplate {
8+
const IconButtonTemplate(super.fileName, super.tokens)
9+
: super(colorSchemePrefix: '_colors.',
10+
);
11+
12+
@override
13+
String generate() => '''
14+
// Generated version ${tokens["version"]}
15+
class _TokenDefaultsM3 extends ButtonStyle {
16+
_TokenDefaultsM3(this.context)
17+
: super(
18+
animationDuration: kThemeChangeDuration,
19+
enableFeedback: true,
20+
alignment: Alignment.center,
21+
);
22+
23+
final BuildContext context;
24+
late final ColorScheme _colors = Theme.of(context).colorScheme;
25+
26+
// No default text style
27+
28+
@override
29+
MaterialStateProperty<Color?>? get backgroundColor =>
30+
ButtonStyleButton.allOrNull<Color>(Colors.transparent);
31+
32+
@override
33+
MaterialStateProperty<Color?>? get foregroundColor =>
34+
MaterialStateProperty.resolveWith((Set<MaterialState> states) {
35+
if (states.contains(MaterialState.disabled))
36+
return ${componentColor('md.comp.icon-button.disabled.icon')};
37+
return ${componentColor('md.comp.icon-button.unselected.icon')};
38+
});
39+
40+
@override
41+
MaterialStateProperty<Color?>? get overlayColor =>
42+
MaterialStateProperty.resolveWith((Set<MaterialState> states) {
43+
if (states.contains(MaterialState.hovered))
44+
return ${componentColor('md.comp.icon-button.unselected.hover.state-layer')};
45+
if (states.contains(MaterialState.focused))
46+
return ${componentColor('md.comp.icon-button.unselected.focus.state-layer')};
47+
if (states.contains(MaterialState.pressed))
48+
return ${componentColor('md.comp.icon-button.unselected.pressed.state-layer')};
49+
return null;
50+
});
51+
52+
// No default shadow color
53+
54+
// No default surface tint color
55+
56+
@override
57+
MaterialStateProperty<double>? get elevation =>
58+
ButtonStyleButton.allOrNull<double>(0.0);
59+
60+
@override
61+
MaterialStateProperty<EdgeInsetsGeometry>? get padding =>
62+
ButtonStyleButton.allOrNull<EdgeInsetsGeometry>(const EdgeInsets.all(8.0));
63+
64+
@override
65+
MaterialStateProperty<Size>? get minimumSize =>
66+
ButtonStyleButton.allOrNull<Size>(const Size(${tokens["md.comp.icon-button.state-layer.size"]}, ${tokens["md.comp.icon-button.state-layer.size"]}));
67+
68+
// No default fixedSize
69+
70+
@override
71+
MaterialStateProperty<Size>? get maximumSize =>
72+
ButtonStyleButton.allOrNull<Size>(Size.infinite);
73+
74+
// No default side
75+
76+
@override
77+
MaterialStateProperty<OutlinedBorder>? get shape =>
78+
ButtonStyleButton.allOrNull<OutlinedBorder>(${shape("md.comp.icon-button.state-layer")});
79+
80+
@override
81+
MaterialStateProperty<MouseCursor?>? get mouseCursor =>
82+
MaterialStateProperty.resolveWith((Set<MaterialState> states) {
83+
if (states.contains(MaterialState.disabled))
84+
return SystemMouseCursors.basic;
85+
return SystemMouseCursors.click;
86+
});
87+
88+
@override
89+
VisualDensity? get visualDensity => Theme.of(context).visualDensity;
90+
91+
@override
92+
MaterialTapTargetSize? get tapTargetSize => Theme.of(context).materialTapTargetSize;
93+
94+
@override
95+
InteractiveInkFeatureFactory? get splashFactory => Theme.of(context).splashFactory;
96+
}
97+
''';
98+
99+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
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 IconButton
6+
7+
import 'package:flutter/material.dart';
8+
9+
void main() {
10+
runApp(const IconButtonApp());
11+
}
12+
13+
class IconButtonApp extends StatelessWidget {
14+
const IconButtonApp({super.key});
15+
16+
@override
17+
Widget build(BuildContext context) {
18+
return MaterialApp(
19+
theme: ThemeData(colorSchemeSeed: const Color(0xff6750a4), useMaterial3: true),
20+
title: 'Icon Button Types',
21+
home: const Scaffold(
22+
body: ButtonTypesExample(),
23+
),
24+
);
25+
}
26+
}
27+
28+
class ButtonTypesExample extends StatelessWidget {
29+
const ButtonTypesExample({super.key});
30+
31+
@override
32+
Widget build(BuildContext context) {
33+
return Padding(
34+
padding: const EdgeInsets.all(4.0),
35+
child: Row(
36+
children: const <Widget>[
37+
Spacer(),
38+
ButtonTypesGroup(enabled: true),
39+
ButtonTypesGroup(enabled: false),
40+
Spacer(),
41+
],
42+
),
43+
);
44+
}
45+
}
46+
47+
class ButtonTypesGroup extends StatelessWidget {
48+
const ButtonTypesGroup({ super.key, required this.enabled });
49+
50+
final bool enabled;
51+
52+
@override
53+
Widget build(BuildContext context) {
54+
final VoidCallback? onPressed = enabled ? () {} : null;
55+
final ColorScheme colors = Theme.of(context).colorScheme;
56+
57+
return Padding(
58+
padding: const EdgeInsets.all(4.0),
59+
child: Column(
60+
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
61+
children: <Widget>[
62+
IconButton(icon: const Icon(Icons.filter_drama), onPressed: onPressed),
63+
64+
// Use a standard IconButton with specific style to implement the
65+
// 'Filled' type.
66+
IconButton(
67+
icon: const Icon(Icons.filter_drama),
68+
onPressed: onPressed,
69+
style: IconButton.styleFrom(
70+
foregroundColor: colors.onPrimary,
71+
backgroundColor: colors.primary,
72+
disabledBackgroundColor: colors.onSurface.withOpacity(0.12),
73+
hoverColor: colors.onPrimary.withOpacity(0.08),
74+
focusColor: colors.onPrimary.withOpacity(0.12),
75+
highlightColor: colors.onPrimary.withOpacity(0.12),
76+
)
77+
),
78+
79+
// Use a standard IconButton with specific style to implement the
80+
// 'Filled Tonal' type.
81+
IconButton(
82+
icon: const Icon(Icons.filter_drama),
83+
onPressed: onPressed,
84+
style: IconButton.styleFrom(
85+
foregroundColor: colors.onSecondaryContainer,
86+
backgroundColor: colors.secondaryContainer,
87+
disabledBackgroundColor: colors.onSurface.withOpacity(0.12),
88+
hoverColor: colors.onSecondaryContainer.withOpacity(0.08),
89+
focusColor: colors.onSecondaryContainer.withOpacity(0.12),
90+
highlightColor: colors.onSecondaryContainer.withOpacity(0.12),
91+
),
92+
),
93+
94+
// Use a standard IconButton with specific style to implement the
95+
// 'Outlined' type.
96+
IconButton(
97+
icon: const Icon(Icons.filter_drama),
98+
onPressed: onPressed,
99+
style: IconButton.styleFrom(
100+
focusColor: colors.onSurfaceVariant.withOpacity(0.12),
101+
highlightColor: colors.onSurface.withOpacity(0.12),
102+
side: onPressed == null
103+
? BorderSide(color: Theme.of(context).colorScheme.onSurface.withOpacity(0.12))
104+
: BorderSide(color: colors.outline),
105+
).copyWith(
106+
foregroundColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
107+
if (states.contains(MaterialState.pressed)) {
108+
return colors.onSurface;
109+
}
110+
return null;
111+
}),
112+
),
113+
),
114+
],
115+
),
116+
);
117+
}
118+
}

0 commit comments

Comments
 (0)