-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Migrate IconButton to Material 3 - Part 1
#105176
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
QuncCccccc
merged 7 commits into
flutter:master
from
QuncCccccc:migrate_icon_button_style
Jun 7, 2022
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
d252e7c
Added standard IconButton for M3 with new ButtonStyle field
QuncCccccc 90cfcee
Added IconButton examples for standart, filled, filled_tonal, and out…
QuncCccccc aad71df
Changed old tests and added new tests
QuncCccccc f252830
Fixed comments about naming and documentation
QuncCccccc e4f8186
Fixed failed analyze
QuncCccccc 113c213
Addressed comments and test failures
QuncCccccc 7f6e9d5
Fixed analyzer failure
QuncCccccc File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| // Copyright 2014 The Flutter Authors. All rights reserved. | ||
| // Use of this source code is governed by a BSD-style license that can be | ||
| // found in the LICENSE file. | ||
|
|
||
| import 'template.dart'; | ||
|
|
||
| class IconButtonTemplate extends TokenTemplate { | ||
| const IconButtonTemplate(super.fileName, super.tokens) | ||
| : super(colorSchemePrefix: '_colors.', | ||
| ); | ||
|
|
||
| @override | ||
| String generate() => ''' | ||
| // Generated version ${tokens["version"]} | ||
| class _TokenDefaultsM3 extends ButtonStyle { | ||
| _TokenDefaultsM3(this.context) | ||
| : super( | ||
| animationDuration: kThemeChangeDuration, | ||
| enableFeedback: true, | ||
| alignment: Alignment.center, | ||
| ); | ||
|
|
||
| final BuildContext context; | ||
| late final ColorScheme _colors = Theme.of(context).colorScheme; | ||
|
|
||
| // No default text style | ||
|
|
||
| @override | ||
| MaterialStateProperty<Color?>? get backgroundColor => | ||
| ButtonStyleButton.allOrNull<Color>(Colors.transparent); | ||
|
|
||
| @override | ||
| MaterialStateProperty<Color?>? get foregroundColor => | ||
| MaterialStateProperty.resolveWith((Set<MaterialState> states) { | ||
| if (states.contains(MaterialState.disabled)) | ||
| return ${componentColor('md.comp.icon-button.disabled.icon')}; | ||
| return ${componentColor('md.comp.icon-button.unselected.icon')}; | ||
| }); | ||
|
|
||
| @override | ||
| MaterialStateProperty<Color?>? get overlayColor => | ||
| MaterialStateProperty.resolveWith((Set<MaterialState> states) { | ||
| if (states.contains(MaterialState.hovered)) | ||
| return ${componentColor('md.comp.icon-button.unselected.hover.state-layer')}; | ||
| if (states.contains(MaterialState.focused)) | ||
| return ${componentColor('md.comp.icon-button.unselected.focus.state-layer')}; | ||
| if (states.contains(MaterialState.pressed)) | ||
| return ${componentColor('md.comp.icon-button.unselected.pressed.state-layer')}; | ||
| return null; | ||
| }); | ||
|
|
||
| // No default shadow color | ||
|
|
||
| // No default surface tint color | ||
|
|
||
| @override | ||
| MaterialStateProperty<double>? get elevation => | ||
| ButtonStyleButton.allOrNull<double>(0.0); | ||
|
|
||
| @override | ||
| MaterialStateProperty<EdgeInsetsGeometry>? get padding => | ||
| ButtonStyleButton.allOrNull<EdgeInsetsGeometry>(const EdgeInsets.all(8.0)); | ||
|
|
||
| @override | ||
| MaterialStateProperty<Size>? get minimumSize => | ||
| ButtonStyleButton.allOrNull<Size>(const Size(${tokens["md.comp.icon-button.state-layer.size"]}, ${tokens["md.comp.icon-button.state-layer.size"]})); | ||
|
|
||
| // No default fixedSize | ||
|
|
||
| @override | ||
| MaterialStateProperty<Size>? get maximumSize => | ||
| ButtonStyleButton.allOrNull<Size>(Size.infinite); | ||
|
|
||
| // No default side | ||
|
|
||
| @override | ||
| MaterialStateProperty<OutlinedBorder>? get shape => | ||
| ButtonStyleButton.allOrNull<OutlinedBorder>(${shape("md.comp.icon-button.state-layer")}); | ||
|
|
||
| @override | ||
| MaterialStateProperty<MouseCursor?>? get mouseCursor => | ||
| MaterialStateProperty.resolveWith((Set<MaterialState> states) { | ||
| if (states.contains(MaterialState.disabled)) | ||
| return SystemMouseCursors.basic; | ||
| return SystemMouseCursors.click; | ||
| }); | ||
|
|
||
| @override | ||
| VisualDensity? get visualDensity => Theme.of(context).visualDensity; | ||
|
|
||
| @override | ||
| MaterialTapTargetSize? get tapTargetSize => Theme.of(context).materialTapTargetSize; | ||
|
|
||
| @override | ||
| InteractiveInkFeatureFactory? get splashFactory => Theme.of(context).splashFactory; | ||
| } | ||
| '''; | ||
|
|
||
| } |
118 changes: 118 additions & 0 deletions
118
examples/api/lib/material/icon_button/icon_button.2.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| // Copyright 2014 The Flutter Authors. All rights reserved. | ||
| // Use of this source code is governed by a BSD-style license that can be | ||
| // found in the LICENSE file. | ||
|
|
||
| // Flutter code sample for IconButton | ||
|
|
||
| import 'package:flutter/material.dart'; | ||
|
|
||
| void main() { | ||
| runApp(const IconButtonApp()); | ||
| } | ||
|
|
||
| class IconButtonApp extends StatelessWidget { | ||
| const IconButtonApp({super.key}); | ||
|
|
||
| @override | ||
| Widget build(BuildContext context) { | ||
| return MaterialApp( | ||
| theme: ThemeData(colorSchemeSeed: const Color(0xff6750a4), useMaterial3: true), | ||
| title: 'Icon Button Types', | ||
| home: const Scaffold( | ||
| body: ButtonTypesExample(), | ||
| ), | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| class ButtonTypesExample extends StatelessWidget { | ||
| const ButtonTypesExample({super.key}); | ||
|
|
||
| @override | ||
| Widget build(BuildContext context) { | ||
| return Padding( | ||
| padding: const EdgeInsets.all(4.0), | ||
| child: Row( | ||
| children: const <Widget>[ | ||
| Spacer(), | ||
| ButtonTypesGroup(enabled: true), | ||
| ButtonTypesGroup(enabled: false), | ||
| Spacer(), | ||
| ], | ||
| ), | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| class ButtonTypesGroup extends StatelessWidget { | ||
| const ButtonTypesGroup({ super.key, required this.enabled }); | ||
|
|
||
| final bool enabled; | ||
|
|
||
| @override | ||
| Widget build(BuildContext context) { | ||
| final VoidCallback? onPressed = enabled ? () {} : null; | ||
| final ColorScheme colors = Theme.of(context).colorScheme; | ||
|
|
||
| return Padding( | ||
| padding: const EdgeInsets.all(4.0), | ||
| child: Column( | ||
| mainAxisAlignment: MainAxisAlignment.spaceEvenly, | ||
| children: <Widget>[ | ||
| IconButton(icon: const Icon(Icons.filter_drama), onPressed: onPressed), | ||
|
|
||
| // Use a standard IconButton with specific style to implement the | ||
| // 'Filled' type. | ||
| IconButton( | ||
| icon: const Icon(Icons.filter_drama), | ||
| onPressed: onPressed, | ||
| style: IconButton.styleFrom( | ||
| foregroundColor: colors.onPrimary, | ||
| backgroundColor: colors.primary, | ||
| disabledBackgroundColor: colors.onSurface.withOpacity(0.12), | ||
| hoverColor: colors.onPrimary.withOpacity(0.08), | ||
| focusColor: colors.onPrimary.withOpacity(0.12), | ||
| highlightColor: colors.onPrimary.withOpacity(0.12), | ||
| ) | ||
| ), | ||
|
|
||
| // Use a standard IconButton with specific style to implement the | ||
| // 'Filled Tonal' type. | ||
| IconButton( | ||
| icon: const Icon(Icons.filter_drama), | ||
| onPressed: onPressed, | ||
| style: IconButton.styleFrom( | ||
| foregroundColor: colors.onSecondaryContainer, | ||
| backgroundColor: colors.secondaryContainer, | ||
| disabledBackgroundColor: colors.onSurface.withOpacity(0.12), | ||
| hoverColor: colors.onSecondaryContainer.withOpacity(0.08), | ||
| focusColor: colors.onSecondaryContainer.withOpacity(0.12), | ||
| highlightColor: colors.onSecondaryContainer.withOpacity(0.12), | ||
| ), | ||
| ), | ||
|
|
||
| // Use a standard IconButton with specific style to implement the | ||
| // 'Outlined' type. | ||
| IconButton( | ||
| icon: const Icon(Icons.filter_drama), | ||
| onPressed: onPressed, | ||
| style: IconButton.styleFrom( | ||
| focusColor: colors.onSurfaceVariant.withOpacity(0.12), | ||
| highlightColor: colors.onSurface.withOpacity(0.12), | ||
| side: onPressed == null | ||
| ? BorderSide(color: Theme.of(context).colorScheme.onSurface.withOpacity(0.12)) | ||
| : BorderSide(color: colors.outline), | ||
| ).copyWith( | ||
| foregroundColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) { | ||
| if (states.contains(MaterialState.pressed)) { | ||
| return colors.onSurface; | ||
| } | ||
| return null; | ||
| }), | ||
| ), | ||
| ), | ||
| ], | ||
| ), | ||
| ); | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should use tokens for these. We should also provide the three default styles, so that devs don't have to copy paste and have a nice starting point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, we have a similar problem with the variants in the common buttons as well. I didn't realize the variants would need that much configuration.
I really wish we had a simpler way of providing these variants without a lot of extra API baggage to go with them (i.e. either full button subclasses for each variant, or named constructors that have to duplicate all the constructor parameters and use an enum or something to indicate which set of defaults to use in the build method).
That said, I would go with this example for this PR and we can look at cleaning up the variant support in a future PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the current Flutter master channel (9160359),
IconButton.styleFromgives me stadium shapes by default. This is consistent with icon_button.dart#L614, below.I believe the spec for
md.sys.shape.corner.fullindicates they should useshape: const CircleBorder()instead.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi, the icon button is migrated to be a subclass of ButtonStyleButton now and has a compact
VisualDensityon desktop platform, so the buttons don't look like circular. If the app is ran on iOS/Android, the buttons will show a standardVisualDensityand have a circular shape.If this is the problem that you are facing, adding
visualDensity: VisualDensity.standardto IconButton or IconButton.styleFrom() will solve the problem on desktop platform:)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@QuncCccccc Interesting, thanks! I didn't appreciate that property. The spec does have a "high-emphasis filled button for End call" in an example, but it's a bit subtle.
For reference, I see the issue in Flutter web.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I assumed you are using a desktop platform. Both desktop and web have a compact visual density as a default value because they have a larger screen, compared to phone devices. So on web, just adding same thing as desktop (
visualDensity: VisualDensity.standard) will provide a circular icon button:)Please let me know if there's any other problems.