Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
16bbef18
Unverified
Commit
16bbef18
authored
Jul 06, 2022
by
Qun Cheng
Committed by
GitHub
Jul 06, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Migrate `IconButton` to Material 3 - Part 2 (#106437)
parent
5a5721c9
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
654 additions
and
19 deletions
+654
-19
icon_button_template.dart
dev/tools/gen_defaults/lib/icon_button_template.dart
+14
-0
icon_button.3.dart
examples/api/lib/material/icon_button/icon_button.3.dart
+192
-0
icon_button.dart
packages/flutter/lib/src/material/icon_button.dart
+187
-8
icon_button_test.dart
packages/flutter/test/material/icon_button_test.dart
+261
-11
No files found.
dev/tools/gen_defaults/lib/icon_button_template.dart
View file @
16bbef18
...
...
@@ -35,12 +35,26 @@ class _TokenDefaultsM3 extends ButtonStyle {
if (states.contains(MaterialState.disabled)) {
return
${componentColor('md.comp.icon-button.disabled.icon')}
;
}
if (states.contains(MaterialState.selected)) {
return
${componentColor('md.comp.icon-button.selected.icon')}
;
}
return
${componentColor('md.comp.icon-button.unselected.icon')}
;
});
@override
MaterialStateProperty<Color?>? get overlayColor =>
MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
if (states.contains(MaterialState.hovered)) {
return
${componentColor('md.comp.icon-button.selected.hover.state-layer')}
;
}
if (states.contains(MaterialState.focused)) {
return
${componentColor('md.comp.icon-button.selected.focus.state-layer')}
;
}
if (states.contains(MaterialState.pressed)) {
return
${componentColor('md.comp.icon-button.selected.pressed.state-layer')}
;
}
}
if (states.contains(MaterialState.hovered)) {
return
${componentColor('md.comp.icon-button.unselected.hover.state-layer')}
;
}
...
...
examples/api/lib/material/icon_button/icon_button.3.dart
0 → 100644
View file @
16bbef18
// 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 with toggle feature
import
'package:flutter/material.dart'
;
void
main
(
)
{
runApp
(
const
IconButtonToggleApp
());
}
class
IconButtonToggleApp
extends
StatelessWidget
{
const
IconButtonToggleApp
({
super
.
key
});
@override
Widget
build
(
BuildContext
context
)
{
return
MaterialApp
(
theme:
ThemeData
(
colorSchemeSeed:
const
Color
(
0xff6750a4
),
useMaterial3:
true
,
// Desktop and web platforms have a compact visual density by default.
// To see buttons with circular background on desktop/web, the "visualDensity"
// needs to be set to "VisualDensity.standard".
visualDensity:
VisualDensity
.
standard
,
),
title:
'Icon Button Types'
,
home:
const
Scaffold
(
body:
DemoIconToggleButtons
(),
),
);
}
}
class
DemoIconToggleButtons
extends
StatefulWidget
{
const
DemoIconToggleButtons
({
super
.
key
});
@override
State
<
DemoIconToggleButtons
>
createState
()
=>
_DemoIconToggleButtonsState
();
}
class
_DemoIconToggleButtonsState
extends
State
<
DemoIconToggleButtons
>
{
@override
Widget
build
(
BuildContext
context
)
{
return
Padding
(
padding:
const
EdgeInsets
.
all
(
8.0
),
child:
Column
(
mainAxisAlignment:
MainAxisAlignment
.
spaceEvenly
,
children:
<
Widget
>[
Row
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
// Standard IconButton
children:
const
<
Widget
>[
DemoIconToggleButton
(
isEnabled:
true
),
SizedBox
(
width:
10
),
DemoIconToggleButton
(
isEnabled:
false
),
]
),
Row
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
const
<
Widget
>[
// Filled IconButton
DemoIconToggleButton
(
isEnabled:
true
,
getDefaultStyle:
enabledFilledButtonStyle
,),
SizedBox
(
width:
10
),
DemoIconToggleButton
(
isEnabled:
false
,
getDefaultStyle:
disabledFilledButtonStyle
,)
]
),
Row
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
const
<
Widget
>[
// Filled Tonal IconButton
DemoIconToggleButton
(
isEnabled:
true
,
getDefaultStyle:
enabledFilledTonalButtonStyle
,),
SizedBox
(
width:
10
),
DemoIconToggleButton
(
isEnabled:
false
,
getDefaultStyle:
disabledFilledTonalButtonStyle
,),
]
),
Row
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
const
<
Widget
>[
// Outlined IconButton
DemoIconToggleButton
(
isEnabled:
true
,
getDefaultStyle:
enabledOutlinedButtonStyle
,),
SizedBox
(
width:
10
),
DemoIconToggleButton
(
isEnabled:
false
,
getDefaultStyle:
disabledOutlinedButtonStyle
,),
]
),
]
),
);
}
}
class
DemoIconToggleButton
extends
StatefulWidget
{
const
DemoIconToggleButton
({
required
this
.
isEnabled
,
this
.
getDefaultStyle
,
super
.
key
});
final
bool
isEnabled
;
final
ButtonStyle
?
Function
(
bool
,
ColorScheme
)?
getDefaultStyle
;
@override
State
<
DemoIconToggleButton
>
createState
()
=>
_DemoIconToggleButtonState
();
}
class
_DemoIconToggleButtonState
extends
State
<
DemoIconToggleButton
>
{
bool
selected
=
false
;
@override
Widget
build
(
BuildContext
context
)
{
final
ColorScheme
colors
=
Theme
.
of
(
context
).
colorScheme
;
final
VoidCallback
?
onPressed
=
widget
.
isEnabled
?
()
{
setState
(()
{
selected
=
!
selected
;
});
}
:
null
;
ButtonStyle
?
style
;
if
(
widget
.
getDefaultStyle
!=
null
)
{
style
=
widget
.
getDefaultStyle
!(
selected
,
colors
);
}
return
IconButton
(
isSelected:
selected
,
icon:
const
Icon
(
Icons
.
settings_outlined
),
selectedIcon:
const
Icon
(
Icons
.
settings
),
onPressed:
onPressed
,
style:
style
,
);
}
}
ButtonStyle
enabledFilledButtonStyle
(
bool
selected
,
ColorScheme
colors
)
{
return
IconButton
.
styleFrom
(
foregroundColor:
selected
?
colors
.
onPrimary
:
colors
.
primary
,
backgroundColor:
selected
?
colors
.
primary
:
colors
.
surfaceVariant
,
disabledForegroundColor:
colors
.
onSurface
.
withOpacity
(
0.38
),
disabledBackgroundColor:
colors
.
onSurface
.
withOpacity
(
0.12
),
hoverColor:
selected
?
colors
.
onPrimary
.
withOpacity
(
0.08
)
:
colors
.
primary
.
withOpacity
(
0.08
),
focusColor:
selected
?
colors
.
onPrimary
.
withOpacity
(
0.12
)
:
colors
.
primary
.
withOpacity
(
0.12
),
highlightColor:
selected
?
colors
.
onPrimary
.
withOpacity
(
0.12
)
:
colors
.
primary
.
withOpacity
(
0.12
),
);
}
ButtonStyle
disabledFilledButtonStyle
(
bool
selected
,
ColorScheme
colors
)
{
return
IconButton
.
styleFrom
(
disabledForegroundColor:
colors
.
onSurface
.
withOpacity
(
0.38
),
disabledBackgroundColor:
colors
.
onSurface
.
withOpacity
(
0.12
),
);
}
ButtonStyle
enabledFilledTonalButtonStyle
(
bool
selected
,
ColorScheme
colors
)
{
return
IconButton
.
styleFrom
(
foregroundColor:
selected
?
colors
.
onSecondaryContainer
:
colors
.
onSurfaceVariant
,
backgroundColor:
selected
?
colors
.
secondaryContainer
:
colors
.
surfaceVariant
,
hoverColor:
selected
?
colors
.
onSecondaryContainer
.
withOpacity
(
0.08
)
:
colors
.
onSurfaceVariant
.
withOpacity
(
0.08
),
focusColor:
selected
?
colors
.
onSecondaryContainer
.
withOpacity
(
0.12
)
:
colors
.
onSurfaceVariant
.
withOpacity
(
0.12
),
highlightColor:
selected
?
colors
.
onSecondaryContainer
.
withOpacity
(
0.12
)
:
colors
.
onSurfaceVariant
.
withOpacity
(
0.12
),
);
}
ButtonStyle
disabledFilledTonalButtonStyle
(
bool
selected
,
ColorScheme
colors
)
{
return
IconButton
.
styleFrom
(
disabledForegroundColor:
colors
.
onSurface
.
withOpacity
(
0.38
),
disabledBackgroundColor:
colors
.
onSurface
.
withOpacity
(
0.12
),
);
}
ButtonStyle
enabledOutlinedButtonStyle
(
bool
selected
,
ColorScheme
colors
)
{
return
IconButton
.
styleFrom
(
backgroundColor:
selected
?
colors
.
inverseSurface
:
null
,
hoverColor:
selected
?
colors
.
onInverseSurface
.
withOpacity
(
0.08
)
:
colors
.
onSurfaceVariant
.
withOpacity
(
0.08
),
focusColor:
selected
?
colors
.
onInverseSurface
.
withOpacity
(
0.12
)
:
colors
.
onSurfaceVariant
.
withOpacity
(
0.12
),
highlightColor:
selected
?
colors
.
onInverseSurface
.
withOpacity
(
0.12
)
:
colors
.
onSurface
.
withOpacity
(
0.12
),
side:
BorderSide
(
color:
colors
.
outline
),
).
copyWith
(
foregroundColor:
MaterialStateProperty
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
selected
))
{
return
colors
.
onInverseSurface
;
}
if
(
states
.
contains
(
MaterialState
.
pressed
))
{
return
colors
.
onSurface
;
}
return
null
;
}),
);
}
ButtonStyle
disabledOutlinedButtonStyle
(
bool
selected
,
ColorScheme
colors
)
{
return
IconButton
.
styleFrom
(
disabledForegroundColor:
colors
.
onSurface
.
withOpacity
(
0.38
),
disabledBackgroundColor:
selected
?
colors
.
onSurface
.
withOpacity
(
0.12
)
:
null
,
side:
selected
?
null
:
BorderSide
(
color:
colors
.
outline
.
withOpacity
(
0.12
)),
);
}
packages/flutter/lib/src/material/icon_button.dart
View file @
16bbef18
...
...
@@ -101,6 +101,14 @@ const double _kMinButtonSize = kMinInteractiveDimension;
/// The default [IconButton] is the standard type, and contained icon buttons can be produced
/// by configuring the [IconButton] widget's properties.
///
/// Material Design 3 also treats [IconButton]s as toggle buttons. In order
/// to not break existing apps, the toggle feature can be optionally controlled
/// by the [isSelected] property.
///
/// If [isSelected] is null it will behave as a normal button. If [isSelected] is not
/// null then it will behave as a toggle button. If [isSelected] is true then it will
/// show [selectedIcon], if it false it will show the normal [icon].
///
/// {@tool dartpad}
/// This sample shows creation of [IconButton] widgets for standard, filled,
/// filled tonal and outlined types, as described in: https://m3.material.io/components/icon-buttons/overview
...
...
@@ -108,6 +116,14 @@ const double _kMinButtonSize = kMinInteractiveDimension;
/// ** See code in examples/api/lib/material/icon_button/icon_button.2.dart **
/// {@end-tool}
///
/// {@tool dartpad}
/// This sample shows creation of [IconButton] widgets with toggle feature for
/// standard, filled, filled tonal and outlined types, as described
/// in: https://m3.material.io/components/icon-buttons/overview
///
/// ** See code in examples/api/lib/material/icon_button/icon_button.3.dart **
/// {@end-tool}
///
/// See also:
///
/// * [Icons], the library of Material Icons.
...
...
@@ -151,6 +167,8 @@ class IconButton extends StatelessWidget {
this
.
enableFeedback
=
true
,
this
.
constraints
,
this
.
style
,
this
.
isSelected
,
this
.
selectedIcon
,
required
this
.
icon
,
})
:
assert
(
padding
!=
null
),
assert
(
alignment
!=
null
),
...
...
@@ -218,12 +236,34 @@ class IconButton extends StatelessWidget {
/// See [Icon], [ImageIcon].
final
Widget
icon
;
/// The color for the button's icon when it has the input focus.
/// The color for the button when it has the input focus.
///
/// If [ThemeData.useMaterial3] is set to true, this [focusColor] will be mapped
/// to be the [ButtonStyle.overlayColor] in focused state, which paints on top of
/// the button, as an overlay. Therefore, using a color with some transparency
/// is recommended. For example, one could customize the [focusColor] below:
///
/// ```dart
/// IconButton(
/// focusColor: Colors.orange.withOpacity(0.3),
/// )
/// ```
///
/// Defaults to [ThemeData.focusColor] of the ambient theme.
final
Color
?
focusColor
;
/// The color for the button's icon when a pointer is hovering over it.
/// The color for the button when a pointer is hovering over it.
///
/// If [ThemeData.useMaterial3] is set to true, this [hoverColor] will be mapped
/// to be the [ButtonStyle.overlayColor] in hovered state, which paints on top of
/// the button, as an overlay. Therefore, using a color with some transparency
/// is recommended. For example, one could customize the [hoverColor] below:
///
/// ```dart
/// IconButton(
/// hoverColor: Colors.orange.withOpacity(0.3),
/// )
/// ```
///
/// Defaults to [ThemeData.hoverColor] of the ambient theme.
final
Color
?
hoverColor
;
...
...
@@ -249,7 +289,9 @@ class IconButton extends StatelessWidget {
/// fill the button area if the touch is held for long enough time. If the splash
/// color has transparency then the highlight and button color will show through.
///
/// If [ThemeData.useMaterial3] is set to true, this will not be used.
/// If [ThemeData.useMaterial3] is set to true, this will not be used. Use
/// [highlightColor] instead to show the overlay color of the button when the button
/// is in the pressed state.
///
/// Defaults to the Theme's splash color, [ThemeData.splashColor].
final
Color
?
splashColor
;
...
...
@@ -259,6 +301,17 @@ class IconButton extends StatelessWidget {
/// button color (if any). If the highlight color has transparency, the button color
/// will show through. The highlight fades in quickly as the button is held down.
///
/// If [ThemeData.useMaterial3] is set to true, this [highlightColor] will be mapped
/// to be the [ButtonStyle.overlayColor] in pressed state, which paints on top
/// of the button, as an overlay. Therefore, using a color with some transparency
/// is recommended. For example, one could customize the [highlightColor] below:
///
/// ```dart
/// IconButton(
/// highlightColor: Colors.orange.withOpacity(0.3),
/// )
/// ```
///
/// Defaults to the Theme's highlight color, [ThemeData.highlightColor].
final
Color
?
highlightColor
;
...
...
@@ -341,6 +394,32 @@ class IconButton extends StatelessWidget {
/// Null by default.
final
ButtonStyle
?
style
;
/// The optional selection state of the icon button.
///
/// If this property is null, the button will behave as a normal push button,
/// otherwise, the button will toggle between showing [icon] and [selectedIcon]
/// based on the value of [isSelected]. If true, it will show [selectedIcon],
/// if false it will show [icon].
///
/// This property is only used if [ThemeData.useMaterial3] is true.
final
bool
?
isSelected
;
/// The icon to display inside the button when [isSelected] is true. This property
/// can be null. The original [icon] will be used for both selected and unselected
/// status if it is null.
///
/// The [Icon.size] and [Icon.color] of the icon is configured automatically
/// based on the [iconSize] and [color] properties using an [IconTheme] and
/// therefore should not be explicitly configured in the icon widget.
///
/// This property is only used if [ThemeData.useMaterial3] is true.
///
/// See also:
///
/// * [Icon], for icons based on glyphs from fonts instead of images.
/// * [ImageIcon], for showing icons from [AssetImage]s or other [ImageProvider]s.
final
Widget
?
selectedIcon
;
/// A static convenience method that constructs an icon button
/// [ButtonStyle] given simple values. This method is only used for Material 3.
///
...
...
@@ -484,11 +563,16 @@ class IconButton extends StatelessWidget {
adjustedStyle
=
style
!.
merge
(
adjustedStyle
);
}
Widget
effectiveIcon
=
icon
;
if
((
isSelected
??
false
)
&&
selectedIcon
!=
null
)
{
effectiveIcon
=
selectedIcon
!;
}
Widget
iconButton
=
IconTheme
.
merge
(
data:
IconThemeData
(
size:
effectiveIconSize
,
),
child:
i
con
,
child:
effectiveI
con
,
);
if
(
tooltip
!=
null
)
{
iconButton
=
Tooltip
(
...
...
@@ -496,11 +580,13 @@ class IconButton extends StatelessWidget {
child:
iconButton
,
);
}
return
_IconButtonM3
(
return
_SelectableIconButton
(
style:
adjustedStyle
,
onPressed:
onPressed
,
autofocus:
autofocus
,
focusNode:
focusNode
,
isSelected:
isSelected
,
child:
iconButton
,
);
}
...
...
@@ -574,12 +660,76 @@ class IconButton extends StatelessWidget {
}
}
class
_SelectableIconButton
extends
StatefulWidget
{
const
_SelectableIconButton
({
this
.
isSelected
,
this
.
style
,
this
.
focusNode
,
required
this
.
autofocus
,
required
this
.
onPressed
,
required
this
.
child
,
});
final
bool
?
isSelected
;
final
ButtonStyle
?
style
;
final
FocusNode
?
focusNode
;
final
bool
autofocus
;
final
VoidCallback
?
onPressed
;
final
Widget
child
;
@override
State
<
_SelectableIconButton
>
createState
()
=>
_SelectableIconButtonState
();
}
class
_SelectableIconButtonState
extends
State
<
_SelectableIconButton
>
{
late
final
MaterialStatesController
statesController
;
@override
void
initState
()
{
super
.
initState
();
if
(
widget
.
isSelected
==
null
)
{
statesController
=
MaterialStatesController
();
}
else
{
statesController
=
MaterialStatesController
(<
MaterialState
>{
if
(
widget
.
isSelected
!)
MaterialState
.
selected
});
}
}
@override
void
didUpdateWidget
(
_SelectableIconButton
oldWidget
)
{
super
.
didUpdateWidget
(
oldWidget
);
if
(
widget
.
isSelected
==
null
)
{
if
(
statesController
.
value
.
contains
(
MaterialState
.
selected
))
{
statesController
.
update
(
MaterialState
.
selected
,
false
);
}
return
;
}
if
(
widget
.
isSelected
!=
oldWidget
.
isSelected
)
{
statesController
.
update
(
MaterialState
.
selected
,
widget
.
isSelected
!);
}
}
@override
Widget
build
(
BuildContext
context
)
{
return
_IconButtonM3
(
statesController:
statesController
,
style:
widget
.
style
,
autofocus:
widget
.
autofocus
,
focusNode:
widget
.
focusNode
,
onPressed:
widget
.
onPressed
,
child:
widget
.
child
,
);
}
}
class
_IconButtonM3
extends
ButtonStyleButton
{
const
_IconButtonM3
({
required
super
.
onPressed
,
super
.
style
,
super
.
focusNode
,
super
.
autofocus
=
false
,
super
.
statesController
,
required
Widget
super
.
child
,
})
:
super
(
onLongPress:
null
,
...
...
@@ -596,8 +746,12 @@ class _IconButtonM3 extends ButtonStyleButton {
/// * `backgroundColor` - transparent
/// * `foregroundColor`
/// * disabled - Theme.colorScheme.onSurface(0.38)
/// * selected - Theme.colorScheme.primary
/// * others - Theme.colorScheme.onSurfaceVariant
/// * `overlayColor`
/// * selected
/// * hovered - Theme.colorScheme.primary(0.08)
/// * focused or pressed - Theme.colorScheme.primary(0.12)
/// * hovered or focused - Theme.colorScheme.onSurfaceVariant(0.08)
/// * pressed - Theme.colorScheme.onSurfaceVariant(0.12)
/// * others - null
...
...
@@ -684,15 +838,26 @@ class _IconButtonDefaultOverlay extends MaterialStateProperty<Color?> {
@override
Color
?
resolve
(
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
selected
))
{
if
(
states
.
contains
(
MaterialState
.
pressed
))
{
return
highlightColor
??
foregroundColor
?.
withOpacity
(
0.12
);
}
if
(
states
.
contains
(
MaterialState
.
hovered
))
{
return
hoverColor
??
foregroundColor
?.
withOpacity
(
0.08
);
}
if
(
states
.
contains
(
MaterialState
.
focused
))
{
return
focusColor
??
foregroundColor
?.
withOpacity
(
0.12
);
}
}
if
(
states
.
contains
(
MaterialState
.
pressed
))
{
return
highlightColor
??
foregroundColor
?.
withOpacity
(
0.12
);
}
if
(
states
.
contains
(
MaterialState
.
hovered
))
{
return
hoverColor
??
foregroundColor
?.
withOpacity
(
0.08
);
}
if
(
states
.
contains
(
MaterialState
.
focused
))
{
return
focusColor
??
foregroundColor
?.
withOpacity
(
0.08
);
}
if
(
states
.
contains
(
MaterialState
.
pressed
))
{
return
highlightColor
??
foregroundColor
?.
withOpacity
(
0.12
);
}
return
null
;
}
...
...
@@ -748,12 +913,26 @@ class _TokenDefaultsM3 extends ButtonStyle {
if
(
states
.
contains
(
MaterialState
.
disabled
))
{
return
_colors
.
onSurface
.
withOpacity
(
0.38
);
}
if
(
states
.
contains
(
MaterialState
.
selected
))
{
return
_colors
.
primary
;
}
return
_colors
.
onSurfaceVariant
;
});
@override
MaterialStateProperty
<
Color
?>?
get
overlayColor
=>
MaterialStateProperty
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
selected
))
{
if
(
states
.
contains
(
MaterialState
.
hovered
))
{
return
_colors
.
primary
.
withOpacity
(
0.08
);
}
if
(
states
.
contains
(
MaterialState
.
focused
))
{
return
_colors
.
primary
.
withOpacity
(
0.12
);
}
if
(
states
.
contains
(
MaterialState
.
pressed
))
{
return
_colors
.
primary
.
withOpacity
(
0.12
);
}
}
if
(
states
.
contains
(
MaterialState
.
hovered
))
{
return
_colors
.
onSurfaceVariant
.
withOpacity
(
0.08
);
}
...
...
packages/flutter/test/material/icon_button_test.dart
View file @
16bbef18
...
...
@@ -1034,7 +1034,7 @@ void main() {
expect
(
material
.
textStyle
,
null
);
expect
(
material
.
type
,
MaterialType
.
button
);
// Disabled
Text
Button
// Disabled
Icon
Button
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
themeM3
,
...
...
@@ -1108,12 +1108,14 @@ void main() {
);
testWidgets
(
'IconButton uses stateful color for icon color in different states - M3'
,
(
WidgetTester
tester
)
async
{
bool
isSelected
=
false
;
final
FocusNode
focusNode
=
FocusNode
();
const
Color
pressedColor
=
Color
(
0x00000001
);
const
Color
hoverColor
=
Color
(
0x00000002
);
const
Color
focusedColor
=
Color
(
0x00000003
);
const
Color
defaultColor
=
Color
(
0x00000004
);
const
Color
selectedColor
=
Color
(
0x00000005
);
Color
getIconColor
(
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
pressed
))
{
...
...
@@ -1125,23 +1127,35 @@ void main() {
if
(
states
.
contains
(
MaterialState
.
focused
))
{
return
focusedColor
;
}
if
(
states
.
contains
(
MaterialState
.
selected
))
{
return
selectedColor
;
}
return
defaultColor
;
}
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
ThemeData
.
from
(
colorScheme:
const
ColorScheme
.
light
(),
useMaterial3:
true
),
home:
Scaffold
(
body:
Center
(
child:
IconButton
(
style:
ButtonStyle
(
foregroundColor:
MaterialStateProperty
.
resolveWith
<
Color
>(
getIconColor
),
home:
StatefulBuilder
(
builder:
(
BuildContext
context
,
StateSetter
setState
)
{
return
Scaffold
(
body:
Center
(
child:
IconButton
(
style:
ButtonStyle
(
foregroundColor:
MaterialStateProperty
.
resolveWith
<
Color
>(
getIconColor
),
),
isSelected:
isSelected
,
onPressed:
()
{
setState
(()
{
isSelected
=
!
isSelected
;
});
},
focusNode:
focusNode
,
icon:
const
Icon
(
Icons
.
ac_unit
),
),
),
onPressed:
()
{},
focusNode:
focusNode
,
icon:
const
Icon
(
Icons
.
ac_unit
),
),
),
);
}
),
),
);
...
...
@@ -1151,6 +1165,12 @@ void main() {
// Default, not disabled.
expect
(
iconColor
(),
equals
(
defaultColor
));
// Selected
final
Finder
button
=
find
.
byType
(
IconButton
);
await
tester
.
tap
(
button
);
await
tester
.
pumpAndSettle
();
expect
(
iconColor
(),
selectedColor
);
// Focused.
focusNode
.
requestFocus
();
await
tester
.
pumpAndSettle
();
...
...
@@ -1319,6 +1339,236 @@ void main() {
);
expect
(
paddingWidget3
.
padding
,
const
EdgeInsets
.
all
(
22
));
});
testWidgets
(
'Default IconButton is not selectable - M3'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
ThemeData
.
from
(
colorScheme:
const
ColorScheme
.
light
(),
useMaterial3:
true
),
home:
IconButton
(
icon:
const
Icon
(
Icons
.
ac_unit
),
onPressed:
(){},)
)
);
final
Finder
button
=
find
.
byType
(
IconButton
);
IconButton
buttonWidget
()
=>
tester
.
widget
<
IconButton
>(
button
);
Material
buttonMaterial
()
{
return
tester
.
widget
<
Material
>(
find
.
descendant
(
of:
find
.
byType
(
IconButton
),
matching:
find
.
byType
(
Material
),
)
);
}
Color
?
iconColor
()
=>
_iconStyle
(
tester
,
Icons
.
ac_unit
)?.
color
;
expect
(
buttonWidget
().
isSelected
,
null
);
expect
(
iconColor
(),
equals
(
const
ColorScheme
.
light
().
onSurfaceVariant
));
expect
(
buttonMaterial
().
color
,
Colors
.
transparent
);
await
tester
.
tap
(
button
);
// The non-toggle IconButton should not change appearance after clicking
await
tester
.
pumpAndSettle
();
expect
(
buttonWidget
().
isSelected
,
null
);
expect
(
iconColor
(),
equals
(
const
ColorScheme
.
light
().
onSurfaceVariant
));
expect
(
buttonMaterial
().
color
,
Colors
.
transparent
);
});
testWidgets
(
'Icon button is selectable when isSelected is not null - M3'
,
(
WidgetTester
tester
)
async
{
bool
isSelected
=
false
;
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
ThemeData
.
from
(
colorScheme:
const
ColorScheme
.
light
(),
useMaterial3:
true
),
home:
StatefulBuilder
(
builder:
(
BuildContext
context
,
StateSetter
setState
)
{
return
IconButton
(
isSelected:
isSelected
,
icon:
const
Icon
(
Icons
.
ac_unit
),
onPressed:
(){
setState
(()
{
isSelected
=
!
isSelected
;
});
},
);
}
)
)
);
final
Finder
button
=
find
.
byType
(
IconButton
);
IconButton
buttonWidget
()
=>
tester
.
widget
<
IconButton
>(
button
);
Color
?
iconColor
()
=>
_iconStyle
(
tester
,
Icons
.
ac_unit
)?.
color
;
Material
buttonMaterial
()
{
return
tester
.
widget
<
Material
>(
find
.
descendant
(
of:
find
.
byType
(
IconButton
),
matching:
find
.
byType
(
Material
),
)
);
}
expect
(
buttonWidget
().
isSelected
,
false
);
expect
(
iconColor
(),
equals
(
const
ColorScheme
.
light
().
onSurfaceVariant
));
expect
(
buttonMaterial
().
color
,
Colors
.
transparent
);
await
tester
.
tap
(
button
);
// The toggle IconButton should change appearance after clicking
await
tester
.
pumpAndSettle
();
expect
(
buttonWidget
().
isSelected
,
true
);
expect
(
iconColor
(),
equals
(
const
ColorScheme
.
light
().
primary
));
expect
(
buttonMaterial
().
color
,
Colors
.
transparent
);
await
tester
.
tap
(
button
);
// The IconButton should be unselected if it's clicked again
await
tester
.
pumpAndSettle
();
expect
(
buttonWidget
().
isSelected
,
false
);
expect
(
iconColor
(),
equals
(
const
ColorScheme
.
light
().
onSurfaceVariant
));
expect
(
buttonMaterial
().
color
,
Colors
.
transparent
);
});
testWidgets
(
'The IconButton is in selected status if isSelected is true by default - M3'
,
(
WidgetTester
tester
)
async
{
bool
isSelected
=
true
;
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
ThemeData
.
from
(
colorScheme:
const
ColorScheme
.
light
(),
useMaterial3:
true
),
home:
StatefulBuilder
(
builder:
(
BuildContext
context
,
StateSetter
setState
)
{
return
IconButton
(
isSelected:
isSelected
,
icon:
const
Icon
(
Icons
.
ac_unit
),
onPressed:
(){
setState
(()
{
isSelected
=
!
isSelected
;
});
},
);
}
)
)
);
final
Finder
button
=
find
.
byType
(
IconButton
);
IconButton
buttonWidget
()
=>
tester
.
widget
<
IconButton
>(
button
);
Color
?
iconColor
()
=>
_iconStyle
(
tester
,
Icons
.
ac_unit
)?.
color
;
Material
buttonMaterial
()
{
return
tester
.
widget
<
Material
>(
find
.
descendant
(
of:
find
.
byType
(
IconButton
),
matching:
find
.
byType
(
Material
),
)
);
}
expect
(
buttonWidget
().
isSelected
,
true
);
expect
(
iconColor
(),
equals
(
const
ColorScheme
.
light
().
primary
));
expect
(
buttonMaterial
().
color
,
Colors
.
transparent
);
await
tester
.
tap
(
button
);
// The IconButton becomes unselected if it's clicked
await
tester
.
pumpAndSettle
();
expect
(
buttonWidget
().
isSelected
,
false
);
expect
(
iconColor
(),
equals
(
const
ColorScheme
.
light
().
onSurfaceVariant
));
expect
(
buttonMaterial
().
color
,
Colors
.
transparent
);
});
testWidgets
(
"The selectedIcon is used if it's not null and the button is clicked"
,
(
WidgetTester
tester
)
async
{
bool
isSelected
=
false
;
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
ThemeData
.
from
(
colorScheme:
const
ColorScheme
.
light
(),
useMaterial3:
true
),
home:
StatefulBuilder
(
builder:
(
BuildContext
context
,
StateSetter
setState
)
{
return
IconButton
(
isSelected:
isSelected
,
selectedIcon:
const
Icon
(
Icons
.
account_box
),
icon:
const
Icon
(
Icons
.
account_box_outlined
),
onPressed:
(){
setState
(()
{
isSelected
=
!
isSelected
;
});
},
);
}
)
)
);
final
Finder
button
=
find
.
byType
(
IconButton
);
expect
(
find
.
byIcon
(
Icons
.
account_box_outlined
),
findsOneWidget
);
expect
(
find
.
byIcon
(
Icons
.
account_box
),
findsNothing
);
await
tester
.
tap
(
button
);
// The icon becomes to selectedIcon
await
tester
.
pumpAndSettle
();
expect
(
find
.
byIcon
(
Icons
.
account_box
),
findsOneWidget
);
expect
(
find
.
byIcon
(
Icons
.
account_box_outlined
),
findsNothing
);
await
tester
.
tap
(
button
);
// The icon becomes the original icon when it's clicked again
await
tester
.
pumpAndSettle
();
expect
(
find
.
byIcon
(
Icons
.
account_box_outlined
),
findsOneWidget
);
expect
(
find
.
byIcon
(
Icons
.
account_box
),
findsNothing
);
});
testWidgets
(
'The original icon is used for selected and unselected status when selectedIcon is null'
,
(
WidgetTester
tester
)
async
{
bool
isSelected
=
false
;
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
ThemeData
.
from
(
colorScheme:
const
ColorScheme
.
light
(),
useMaterial3:
true
),
home:
StatefulBuilder
(
builder:
(
BuildContext
context
,
StateSetter
setState
)
{
return
IconButton
(
isSelected:
isSelected
,
icon:
const
Icon
(
Icons
.
account_box
),
onPressed:
(){
setState
(()
{
isSelected
=
!
isSelected
;
});
},
);
}
)
)
);
final
Finder
button
=
find
.
byType
(
IconButton
);
IconButton
buttonWidget
()
=>
tester
.
widget
<
IconButton
>(
button
);
expect
(
buttonWidget
().
isSelected
,
false
);
expect
(
buttonWidget
().
selectedIcon
,
null
);
expect
(
find
.
byIcon
(
Icons
.
account_box
),
findsOneWidget
);
await
tester
.
tap
(
button
);
// The icon becomes the original icon when it's clicked again
await
tester
.
pumpAndSettle
();
expect
(
buttonWidget
().
isSelected
,
true
);
expect
(
buttonWidget
().
selectedIcon
,
null
);
expect
(
find
.
byIcon
(
Icons
.
account_box
),
findsOneWidget
);
});
testWidgets
(
'The selectedIcon is used for disabled button if isSelected is true - M3'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
ThemeData
.
from
(
colorScheme:
const
ColorScheme
.
light
(),
useMaterial3:
true
),
home:
const
IconButton
(
isSelected:
true
,
icon:
Icon
(
Icons
.
account_box
),
selectedIcon:
Icon
(
Icons
.
ac_unit
),
onPressed:
null
,
)
)
);
final
Finder
button
=
find
.
byType
(
IconButton
);
IconButton
buttonWidget
()
=>
tester
.
widget
<
IconButton
>(
button
);
expect
(
buttonWidget
().
isSelected
,
true
);
expect
(
find
.
byIcon
(
Icons
.
account_box
),
findsNothing
);
expect
(
find
.
byIcon
(
Icons
.
ac_unit
),
findsOneWidget
);
});
}
Widget
wrap
(
{
required
Widget
child
,
required
bool
useMaterial3
})
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment