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
5454bab6
Unverified
Commit
5454bab6
authored
Aug 25, 2022
by
Darren Austin
Committed by
GitHub
Aug 25, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Added support for M3 filled and filled tonal buttons. (#107382)
parent
e6ed4c46
Changes
17
Show whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
2993 additions
and
45 deletions
+2993
-45
gen_defaults.dart
dev/tools/gen_defaults/bin/gen_defaults.dart
+2
-0
button_style.0.dart
examples/api/lib/material/button_style/button_style.0.dart
+2
-24
filled_button.0.dart
examples/api/lib/material/filled_button/filled_button.0.dart
+61
-0
material.dart
packages/flutter/lib/material.dart
+2
-0
button.dart
packages/flutter/lib/src/material/button.dart
+6
-4
button_style.dart
packages/flutter/lib/src/material/button_style.dart
+6
-5
button_style_button.dart
packages/flutter/lib/src/material/button_style_button.dart
+9
-5
button_theme.dart
packages/flutter/lib/src/material/button_theme.dart
+2
-1
elevated_button.dart
packages/flutter/lib/src/material/elevated_button.dart
+4
-2
filled_button.dart
packages/flutter/lib/src/material/filled_button.dart
+737
-0
filled_button_theme.dart
packages/flutter/lib/src/material/filled_button_theme.dart
+128
-0
outlined_button.dart
packages/flutter/lib/src/material/outlined_button.dart
+4
-2
text_button.dart
packages/flutter/lib/src/material/text_button.dart
+3
-1
theme_data.dart
packages/flutter/lib/src/material/theme_data.dart
+17
-1
filled_button_test.dart
packages/flutter/test/material/filled_button_test.dart
+1752
-0
filled_button_theme_test.dart
packages/flutter/test/material/filled_button_theme_test.dart
+253
-0
theme_data_test.dart
packages/flutter/test/material/theme_data_test.dart
+5
-0
No files found.
dev/tools/gen_defaults/bin/gen_defaults.dart
View file @
5454bab6
...
...
@@ -103,6 +103,8 @@ Future<void> main(List<String> args) async {
AppBarTemplate
(
'AppBar'
,
'
$materialLib
/app_bar.dart'
,
tokens
).
updateFile
();
ButtonTemplate
(
'md.comp.elevated-button'
,
'ElevatedButton'
,
'
$materialLib
/elevated_button.dart'
,
tokens
).
updateFile
();
ButtonTemplate
(
'md.comp.filled-button'
,
'FilledButton'
,
'
$materialLib
/filled_button.dart'
,
tokens
).
updateFile
();
ButtonTemplate
(
'md.comp.filled-tonal-button'
,
'FilledTonalButton'
,
'
$materialLib
/filled_button.dart'
,
tokens
).
updateFile
();
ButtonTemplate
(
'md.comp.outlined-button'
,
'OutlinedButton'
,
'
$materialLib
/outlined_button.dart'
,
tokens
).
updateFile
();
ButtonTemplate
(
'md.comp.text-button'
,
'TextButton'
,
'
$materialLib
/text_button.dart'
,
tokens
).
updateFile
();
CardTemplate
(
'Card'
,
'
$materialLib
/card.dart'
,
tokens
).
updateFile
();
...
...
examples/api/lib/material/button_style/button_style.0.dart
View file @
5454bab6
...
...
@@ -58,31 +58,9 @@ class ButtonTypesGroup extends StatelessWidget {
mainAxisAlignment:
MainAxisAlignment
.
spaceEvenly
,
children:
<
Widget
>[
ElevatedButton
(
onPressed:
onPressed
,
child:
const
Text
(
'Elevated'
)),
// Use an ElevatedButton with specific style to implement the
// 'Filled' type.
ElevatedButton
(
style:
ElevatedButton
.
styleFrom
(
foregroundColor:
Theme
.
of
(
context
).
colorScheme
.
onPrimary
,
backgroundColor:
Theme
.
of
(
context
).
colorScheme
.
primary
,
).
copyWith
(
elevation:
ButtonStyleButton
.
allOrNull
(
0.0
)),
onPressed:
onPressed
,
child:
const
Text
(
'Filled'
),
),
// Use an ElevatedButton with specific style to implement the
// 'Filled Tonal' type.
ElevatedButton
(
style:
ElevatedButton
.
styleFrom
(
foregroundColor:
Theme
.
of
(
context
).
colorScheme
.
onSecondaryContainer
,
backgroundColor:
Theme
.
of
(
context
).
colorScheme
.
secondaryContainer
,
).
copyWith
(
elevation:
ButtonStyleButton
.
allOrNull
(
0.0
)),
onPressed:
onPressed
,
child:
const
Text
(
'Filled Tonal'
),
),
FilledButton
(
onPressed:
onPressed
,
child:
const
Text
(
'Filled'
)),
FilledButton
.
tonal
(
onPressed:
onPressed
,
child:
const
Text
(
'Filled Tonal'
)),
OutlinedButton
(
onPressed:
onPressed
,
child:
const
Text
(
'Outlined'
)),
TextButton
(
onPressed:
onPressed
,
child:
const
Text
(
'Text'
)),
],
),
...
...
examples/api/lib/material/filled_button/filled_button.0.dart
0 → 100644
View file @
5454bab6
// 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 FilledButton
import
'package:flutter/material.dart'
;
void
main
(
)
{
runApp
(
const
FilledButtonApp
());
}
class
FilledButtonApp
extends
StatelessWidget
{
const
FilledButtonApp
({
super
.
key
});
@override
Widget
build
(
BuildContext
context
)
{
return
MaterialApp
(
theme:
ThemeData
(
colorSchemeSeed:
const
Color
(
0xff6750a4
),
useMaterial3:
true
),
home:
Scaffold
(
appBar:
AppBar
(
title:
const
Text
(
'FilledButton Sample'
)),
body:
Center
(
child:
Row
(
mainAxisSize:
MainAxisSize
.
min
,
children:
<
Widget
>[
Column
(
children:
<
Widget
>[
const
SizedBox
(
height:
30
),
const
Text
(
'Filled'
),
const
SizedBox
(
height:
15
),
FilledButton
(
onPressed:
()
{},
child:
const
Text
(
'Enabled'
),
),
const
SizedBox
(
height:
30
),
const
FilledButton
(
onPressed:
null
,
child:
Text
(
'Disabled'
),
),
]),
const
SizedBox
(
width:
30
),
Column
(
children:
<
Widget
>[
const
SizedBox
(
height:
30
),
const
Text
(
'Filled tonal'
),
const
SizedBox
(
height:
15
),
FilledButton
.
tonal
(
onPressed:
()
{},
child:
const
Text
(
'Enabled'
),
),
const
SizedBox
(
height:
30
),
const
FilledButton
.
tonal
(
onPressed:
null
,
child:
Text
(
'Disabled'
),
),
])
],
),
),
),
);
}
}
packages/flutter/lib/material.dart
View file @
5454bab6
...
...
@@ -80,6 +80,8 @@ export 'src/material/expansion_panel.dart';
export
'src/material/expansion_tile.dart'
;
export
'src/material/expansion_tile_theme.dart'
;
export
'src/material/feedback.dart'
;
export
'src/material/filled_button.dart'
;
export
'src/material/filled_button_theme.dart'
;
export
'src/material/filter_chip.dart'
;
export
'src/material/flexible_space_bar.dart'
;
export
'src/material/floating_action_button.dart'
;
...
...
packages/flutter/lib/src/material/button.dart
View file @
5454bab6
...
...
@@ -26,14 +26,16 @@ import 'theme_data.dart';
/// from the themes or from app-specific sources.
///
/// This class is planned to be deprecated in a future release, see
/// [ButtonStyleButton], the base class of [
TextButton], [ElevatedButton], and
/// [OutlinedButton].
/// [ButtonStyleButton], the base class of [
ElevatedButton], [FilledButton],
/// [OutlinedButton]
and [TextButton]
.
///
/// See also:
///
/// * [TextButton], a simple flat button without a shadow.
/// * [ElevatedButton], a filled button whose material elevates when pressed.
/// * [OutlinedButton], a [TextButton] with a border outline.
/// * [FilledButton], a filled button that doesn't elevate when pressed.
/// * [FilledButton.tonal], a filled button variant that uses a secondary fill color.
/// * [OutlinedButton], a button with an outlined border and no fill color.
/// * [TextButton], a button with no outline or fill color.
@Category
(<
String
>[
'Material'
,
'Button'
])
class
RawMaterialButton
extends
StatefulWidget
{
/// Create a button based on [Semantics], [Material], and [InkWell] widgets.
...
...
packages/flutter/lib/src/material/button_style.dart
View file @
5454bab6
...
...
@@ -78,8 +78,8 @@ import 'theme_data.dart';
/// useful to make relatively sweeping changes based on a few initial
/// parameters with simple values. The button styleFrom() methods
/// enable such sweeping changes. See for example:
/// [
TextButton.styleFrom], [Elevat
edButton.styleFrom],
/// [OutlinedButton.styleFrom].
/// [
ElevatedButton.styleFrom], [Fill
edButton.styleFrom],
/// [OutlinedButton.styleFrom]
, [TextButton.styleFrom]
.
///
/// For example, to override the default text and icon colors for a
/// [TextButton], as well as its overlay color, with all of the
...
...
@@ -119,8 +119,8 @@ import 'theme_data.dart';
/// | Type | Flutter implementation |
/// | :----------- | :---------------------- |
/// | Elevated | [ElevatedButton] |
/// | Filled |
Styled [ElevatedButton]
|
/// | Filled Tonal |
Styled [ElevatedButton]
|
/// | Filled |
[FilledButton]
|
/// | Filled Tonal |
[FilledButton.tonal]
|
/// | Outlined | [OutlinedButton] |
/// | Text | [TextButton] |
///
...
...
@@ -132,9 +132,10 @@ import 'theme_data.dart';
///
/// See also:
///
/// * [TextButtonTheme], the theme for [TextButton]s.
/// * [ElevatedButtonTheme], the theme for [ElevatedButton]s.
/// * [FilledButtonTheme], the theme for [FilledButton]s.
/// * [OutlinedButtonTheme], the theme for [OutlinedButton]s.
/// * [TextButtonTheme], the theme for [TextButton]s.
@immutable
class
ButtonStyle
with
Diagnosticable
{
/// Create a [ButtonStyle].
...
...
packages/flutter/lib/src/material/button_style_button.dart
View file @
5454bab6
...
...
@@ -21,10 +21,13 @@ import 'theme_data.dart';
/// Concrete subclasses must override [defaultStyleOf] and [themeStyleOf].
///
/// See also:
///
/// * [TextButton], a simple ButtonStyleButton without a shadow.
/// * [ElevatedButton], a filled ButtonStyleButton whose material elevates when pressed.
/// * [OutlinedButton], similar to [TextButton], but with an outline.
/// * [ElevatedButton], a filled button whose material elevates when pressed.
/// * [FilledButton], a filled button that doesn't elevate when pressed.
/// * [FilledButton.tonal], a filled button variant that uses a secondary fill color.
/// * [OutlinedButton], a button with an outlined border and no fill color.
/// * [TextButton], a button with no outline or fill color.
/// * <https://m3.material.io/components/buttons/overview>, an overview of each of
/// the Material Design button types and how they should be used in designs.
abstract
class
ButtonStyleButton
extends
StatefulWidget
{
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
...
...
@@ -191,9 +194,10 @@ abstract class ButtonStyleButton extends StatefulWidget {
/// See also:
///
/// * [ButtonStyleButton], the [StatefulWidget] subclass for which this class is the [State].
/// * [TextButton], a simple button without a shadow.
/// * [ElevatedButton], a filled button whose material elevates when pressed.
/// * [FilledButton], a filled ButtonStyleButton that doesn't elevate when pressed.
/// * [OutlinedButton], similar to [TextButton], but with an outline.
/// * [TextButton], a simple button without a shadow.
class
_ButtonStyleState
extends
State
<
ButtonStyleButton
>
with
TickerProviderStateMixin
{
AnimationController
?
controller
;
double
?
elevation
;
...
...
packages/flutter/lib/src/material/button_theme.dart
View file @
5454bab6
...
...
@@ -48,9 +48,10 @@ enum ButtonBarLayoutBehavior {
/// This class is planned to be deprecated in a future release.
/// Please use one or more of these buttons and associated themes instead:
///
/// * [TextButton], [TextButtonTheme], [TextButtonThemeData],
/// * [ElevatedButton], [ElevatedButtonTheme], [ElevatedButtonThemeData],
/// * [FilledButton], [FilledButtonTheme], [FilledButtonThemeData],
/// * [OutlinedButton], [OutlinedButtonTheme], [OutlinedButtonThemeData]
/// * [TextButton], [TextButtonTheme], [TextButtonThemeData],
///
/// A button theme can be specified as part of the overall Material theme
/// using [ThemeData.buttonTheme]. The Material theme's button theme data
...
...
packages/flutter/lib/src/material/elevated_button.dart
View file @
5454bab6
...
...
@@ -54,8 +54,10 @@ import 'theme_data.dart';
///
/// See also:
///
/// * [TextButton], a simple flat button without a shadow.
/// * [OutlinedButton], a [TextButton] with a border outline.
/// * [FilledButton], a filled button that doesn't elevate when pressed.
/// * [FilledButton.tonal], a filled button variant that uses a secondary fill color.
/// * [OutlinedButton], a button with an outlined border and no fill color.
/// * [TextButton], a button with no outline or fill color.
/// * <https://material.io/design/components/buttons.html>
/// * <https://m3.material.io/components/buttons>
class
ElevatedButton
extends
ButtonStyleButton
{
...
...
packages/flutter/lib/src/material/filled_button.dart
0 → 100644
View file @
5454bab6
// 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
'dart:math'
as
math
;
import
'dart:ui'
show
lerpDouble
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/widgets.dart'
;
import
'button_style.dart'
;
import
'button_style_button.dart'
;
import
'color_scheme.dart'
;
import
'constants.dart'
;
import
'filled_button_theme.dart'
;
import
'ink_well.dart'
;
import
'material_state.dart'
;
import
'theme.dart'
;
import
'theme_data.dart'
;
enum
_FilledButtonVariant
{
filled
,
tonal
}
/// A Material Design filled button.
///
/// Filled buttons have the most visual impact after the [FloatingActionButton],
/// and should be used for important, final actions that complete a flow,
/// like **Save**, **Join now**, or **Confirm**.
///
/// A filled button is a label [child] displayed on a [Material]
/// widget. The label's [Text] and [Icon] widgets are displayed in
/// [style]'s [ButtonStyle.foregroundColor] and the button's filled
/// background is the [ButtonStyle.backgroundColor].
///
/// The filled button's default style is defined by
/// [defaultStyleOf]. The style of this filled button can be
/// overridden with its [style] parameter. The style of all filled
/// buttons in a subtree can be overridden with the
/// [FilledButtonTheme], and the style of all of the filled
/// buttons in an app can be overridden with the [Theme]'s
/// [ThemeData.filledButtonTheme] property.
///
/// The static [styleFrom] method is a convenient way to create a
/// filled button [ButtonStyle] from simple values.
///
/// If [onPressed] and [onLongPress] callbacks are null, then the
/// button will be disabled.
///
/// To create a 'filled tonal' button, use [FilledButton.tonal].
///
/// {@tool dartpad}
/// This sample produces enabled and disabled filled and filled tonal
/// buttons.
///
/// ** See code in examples/api/lib/material/filled_button/filled_button.0.dart **
/// {@end-tool}
///
/// See also:
///
/// * [ElevatedButton], a filled button whose material elevates when pressed.
/// * [OutlinedButton], a button with an outlined border and no fill color.
/// * [TextButton], a button with no outline or fill color.
/// * <https://material.io/design/components/buttons.html>
/// * <https://m3.material.io/components/buttons>
class
FilledButton
extends
ButtonStyleButton
{
/// Create a FilledButton.
///
/// The [autofocus] and [clipBehavior] arguments must not be null.
const
FilledButton
({
super
.
key
,
required
super
.
onPressed
,
super
.
onLongPress
,
super
.
onHover
,
super
.
onFocusChange
,
super
.
style
,
super
.
focusNode
,
super
.
autofocus
=
false
,
super
.
clipBehavior
=
Clip
.
none
,
super
.
statesController
,
required
super
.
child
,
})
:
_variant
=
_FilledButtonVariant
.
filled
;
/// Create a filled button from [icon] and [label].
///
/// The icon and label are arranged in a row with padding at the start and end
/// and a gap between them.
///
/// The [icon] and [label] arguments must not be null.
factory
FilledButton
.
icon
({
Key
?
key
,
required
VoidCallback
?
onPressed
,
VoidCallback
?
onLongPress
,
ValueChanged
<
bool
>?
onHover
,
ValueChanged
<
bool
>?
onFocusChange
,
ButtonStyle
?
style
,
FocusNode
?
focusNode
,
bool
?
autofocus
,
Clip
?
clipBehavior
,
required
Widget
icon
,
required
Widget
label
,
})
=
_FilledButtonWithIcon
;
/// Create a tonal variant of FilledButton.
///
/// A filled tonal button is an alternative middle ground between
/// [FilledButton] and [OutlinedButton]. They’re useful in contexts where
/// a lower-priority button requires slightly more emphasis than an
/// outline would give, such as "Next" in an onboarding flow.
///
/// The [autofocus] and [clipBehavior] arguments must not be null.
const
FilledButton
.
tonal
({
super
.
key
,
required
super
.
onPressed
,
super
.
onLongPress
,
super
.
onHover
,
super
.
onFocusChange
,
super
.
style
,
super
.
focusNode
,
super
.
autofocus
=
false
,
super
.
clipBehavior
=
Clip
.
none
,
super
.
statesController
,
required
super
.
child
,
})
:
_variant
=
_FilledButtonVariant
.
tonal
;
/// Create a filled tonal button from [icon] and [label].
///
/// The icon and label are arranged in a row with padding at the start and end
/// and a gap between them.
///
/// The [icon] and [label] arguments must not be null.
factory
FilledButton
.
tonalIcon
({
Key
?
key
,
required
VoidCallback
?
onPressed
,
VoidCallback
?
onLongPress
,
ValueChanged
<
bool
>?
onHover
,
ValueChanged
<
bool
>?
onFocusChange
,
ButtonStyle
?
style
,
FocusNode
?
focusNode
,
bool
?
autofocus
,
Clip
?
clipBehavior
,
required
Widget
icon
,
required
Widget
label
,
})
{
return
_FilledButtonWithIcon
.
tonal
(
key:
key
,
onPressed:
onPressed
,
onLongPress:
onLongPress
,
onHover:
onHover
,
onFocusChange:
onFocusChange
,
style:
style
,
focusNode:
focusNode
,
autofocus:
autofocus
,
clipBehavior:
clipBehavior
,
icon:
icon
,
label:
label
,
);
}
/// A static convenience method that constructs a filled button
/// [ButtonStyle] given simple values.
///
/// The [foregroundColor], and [disabledForegroundColor] colors are used to create a
/// [MaterialStateProperty] [ButtonStyle.foregroundColor] value. The
/// [backgroundColor] and [disabledBackgroundColor] are used to create a
/// [MaterialStateProperty] [ButtonStyle.backgroundColor] value.
///
/// The button's elevations are defined relative to the [elevation]
/// parameter. The disabled elevation is the same as the parameter
/// value, [elevation] + 2 is used when the button is hovered
/// or focused, and elevation + 6 is used when the button is pressed.
///
/// Similarly, the [enabledMouseCursor] and [disabledMouseCursor]
/// parameters are used to construct [ButtonStyle.mouseCursor].
///
/// All of the other parameters are either used directly or used to
/// create a [MaterialStateProperty] with a single value for all
/// states.
///
/// All parameters default to null, by default this method returns
/// a [ButtonStyle] that doesn't override anything.
///
/// For example, to override the default text and icon colors for a
/// [FilledButton], as well as its overlay color, with all of the
/// standard opacity adjustments for the pressed, focused, and
/// hovered states, one could write:
///
/// ```dart
/// FilledButton(
/// style: FilledButton.styleFrom(foregroundColor: Colors.green),
/// onPressed: () {},
/// child: const Text('Filled button'),
/// );
/// ```
///
/// or for a Filled tonal variant:
/// ```dart
/// FilledButton.tonal(
/// style: FilledButton.styleFrom(foregroundColor: Colors.green),
/// onPressed: () {},
/// child: const Text('Filled tonal button'),
/// );
/// ```
static
ButtonStyle
styleFrom
({
Color
?
foregroundColor
,
Color
?
backgroundColor
,
Color
?
disabledForegroundColor
,
Color
?
disabledBackgroundColor
,
Color
?
shadowColor
,
Color
?
surfaceTintColor
,
double
?
elevation
,
TextStyle
?
textStyle
,
EdgeInsetsGeometry
?
padding
,
Size
?
minimumSize
,
Size
?
fixedSize
,
Size
?
maximumSize
,
BorderSide
?
side
,
OutlinedBorder
?
shape
,
MouseCursor
?
enabledMouseCursor
,
MouseCursor
?
disabledMouseCursor
,
VisualDensity
?
visualDensity
,
MaterialTapTargetSize
?
tapTargetSize
,
Duration
?
animationDuration
,
bool
?
enableFeedback
,
AlignmentGeometry
?
alignment
,
InteractiveInkFeatureFactory
?
splashFactory
,
})
{
final
MaterialStateProperty
<
Color
?>?
backgroundColorProp
=
(
backgroundColor
==
null
&&
disabledBackgroundColor
==
null
)
?
null
:
_FilledButtonDefaultColor
(
backgroundColor
,
disabledBackgroundColor
);
final
Color
?
foreground
=
foregroundColor
;
final
Color
?
disabledForeground
=
disabledForegroundColor
;
final
MaterialStateProperty
<
Color
?>?
foregroundColorProp
=
(
foreground
==
null
&&
disabledForeground
==
null
)
?
null
:
_FilledButtonDefaultColor
(
foreground
,
disabledForeground
);
final
MaterialStateProperty
<
Color
?>?
overlayColor
=
(
foreground
==
null
)
?
null
:
_FilledButtonDefaultOverlay
(
foreground
);
final
MaterialStateProperty
<
MouseCursor
?>?
mouseCursor
=
(
enabledMouseCursor
==
null
&&
disabledMouseCursor
==
null
)
?
null
:
_FilledButtonDefaultMouseCursor
(
enabledMouseCursor
,
disabledMouseCursor
);
return
ButtonStyle
(
textStyle:
MaterialStatePropertyAll
<
TextStyle
?>(
textStyle
),
backgroundColor:
backgroundColorProp
,
foregroundColor:
foregroundColorProp
,
overlayColor:
overlayColor
,
shadowColor:
ButtonStyleButton
.
allOrNull
<
Color
>(
shadowColor
),
surfaceTintColor:
ButtonStyleButton
.
allOrNull
<
Color
>(
surfaceTintColor
),
elevation:
ButtonStyleButton
.
allOrNull
(
elevation
),
padding:
ButtonStyleButton
.
allOrNull
<
EdgeInsetsGeometry
>(
padding
),
minimumSize:
ButtonStyleButton
.
allOrNull
<
Size
>(
minimumSize
),
fixedSize:
ButtonStyleButton
.
allOrNull
<
Size
>(
fixedSize
),
maximumSize:
ButtonStyleButton
.
allOrNull
<
Size
>(
maximumSize
),
side:
ButtonStyleButton
.
allOrNull
<
BorderSide
>(
side
),
shape:
ButtonStyleButton
.
allOrNull
<
OutlinedBorder
>(
shape
),
mouseCursor:
mouseCursor
,
visualDensity:
visualDensity
,
tapTargetSize:
tapTargetSize
,
animationDuration:
animationDuration
,
enableFeedback:
enableFeedback
,
alignment:
alignment
,
splashFactory:
splashFactory
,
);
}
final
_FilledButtonVariant
_variant
;
/// Defines the button's default appearance.
///
/// The button [child]'s [Text] and [Icon] widgets are rendered with
/// the [ButtonStyle]'s foreground color. The button's [InkWell] adds
/// the style's overlay color when the button is focused, hovered
/// or pressed. The button's background color becomes its [Material]
/// color.
///
/// All of the ButtonStyle's defaults appear below. In this list
/// "Theme.foo" is shorthand for `Theme.of(context).foo`. Color
/// scheme values like "onSurface(0.38)" are shorthand for
/// `onSurface.withOpacity(0.38)`. [MaterialStateProperty] valued
/// properties that are not followed by a sublist have the same
/// value for all states, otherwise the values are as specified for
/// each state, and "others" means all other states.
///
/// The `textScaleFactor` is the value of
/// `MediaQuery.of(context).textScaleFactor` and the names of the
/// EdgeInsets constructors and `EdgeInsetsGeometry.lerp` have been
/// abbreviated for readability.
///
/// The color of the [ButtonStyle.textStyle] is not used, the
/// [ButtonStyle.foregroundColor] color is used instead.
///
/// * `textStyle` - Theme.textTheme.labelLarge
/// * `backgroundColor`
/// * disabled - Theme.colorScheme.onSurface(0.12)
/// * others - Theme.colorScheme.secondaryContainer
/// * `foregroundColor`
/// * disabled - Theme.colorScheme.onSurface(0.38)
/// * others - Theme.colorScheme.onSecondaryContainer
/// * `overlayColor`
/// * hovered - Theme.colorScheme.onSecondaryContainer(0.08)
/// * focused or pressed - Theme.colorScheme.onSecondaryContainer(0.12)
/// * `shadowColor` - Theme.colorScheme.shadow
/// * `surfaceTintColor` - null
/// * `elevation`
/// * disabled - 0
/// * default - 0
/// * hovered - 1
/// * focused or pressed - 0
/// * `padding`
/// * `textScaleFactor <= 1` - horizontal(16)
/// * `1 < textScaleFactor <= 2` - lerp(horizontal(16), horizontal(8))
/// * `2 < textScaleFactor <= 3` - lerp(horizontal(8), horizontal(4))
/// * `3 < textScaleFactor` - horizontal(4)
/// * `minimumSize` - Size(64, 40)
/// * `fixedSize` - null
/// * `maximumSize` - Size.infinite
/// * `side` - null
/// * `shape` - StadiumBorder()
/// * `mouseCursor`
/// * disabled - SystemMouseCursors.basic
/// * others - SystemMouseCursors.click
/// * `visualDensity` - Theme.visualDensity
/// * `tapTargetSize` - Theme.materialTapTargetSize
/// * `animationDuration` - kThemeChangeDuration
/// * `enableFeedback` - true
/// * `alignment` - Alignment.center
/// * `splashFactory` - Theme.splashFactory
///
/// The default padding values for the [FilledButton.icon] factory are slightly different:
///
/// * `padding`
/// * `textScaleFactor <= 1` - start(12) end(16)
/// * `1 < textScaleFactor <= 2` - lerp(start(12) end(16), horizontal(8))
/// * `2 < textScaleFactor <= 3` - lerp(horizontal(8), horizontal(4))
/// * `3 < textScaleFactor` - horizontal(4)
///
/// The default value for `side`, which defines the appearance of the button's
/// outline, is null. That means that the outline is defined by the button
/// shape's [OutlinedBorder.side]. Typically the default value of an
/// [OutlinedBorder]'s side is [BorderSide.none], so an outline is not drawn.
///
@override
ButtonStyle
defaultStyleOf
(
BuildContext
context
)
{
switch
(
_variant
)
{
case
_FilledButtonVariant
.
filled
:
return
_FilledButtonDefaultsM3
(
context
);
case
_FilledButtonVariant
.
tonal
:
return
_FilledTonalButtonDefaultsM3
(
context
);
}
}
/// Returns the [FilledButtonThemeData.style] of the closest
/// [FilledButtonTheme] ancestor.
@override
ButtonStyle
?
themeStyleOf
(
BuildContext
context
)
{
return
FilledButtonTheme
.
of
(
context
).
style
;
}
}
EdgeInsetsGeometry
_scaledPadding
(
BuildContext
context
)
{
return
ButtonStyleButton
.
scaledPadding
(
const
EdgeInsets
.
symmetric
(
horizontal:
16
),
const
EdgeInsets
.
symmetric
(
horizontal:
8
),
const
EdgeInsets
.
symmetric
(
horizontal:
4
),
MediaQuery
.
maybeOf
(
context
)?.
textScaleFactor
??
1
,
);
}
@immutable
class
_FilledButtonDefaultColor
extends
MaterialStateProperty
<
Color
?>
with
Diagnosticable
{
_FilledButtonDefaultColor
(
this
.
color
,
this
.
disabled
);
final
Color
?
color
;
final
Color
?
disabled
;
@override
Color
?
resolve
(
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
disabled
))
{
return
disabled
;
}
return
color
;
}
}
@immutable
class
_FilledButtonDefaultOverlay
extends
MaterialStateProperty
<
Color
?>
with
Diagnosticable
{
_FilledButtonDefaultOverlay
(
this
.
overlay
);
final
Color
overlay
;
@override
Color
?
resolve
(
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
hovered
))
{
return
overlay
.
withOpacity
(
0.08
);
}
if
(
states
.
contains
(
MaterialState
.
focused
)
||
states
.
contains
(
MaterialState
.
pressed
))
{
return
overlay
.
withOpacity
(
0.12
);
}
return
null
;
}
}
@immutable
class
_FilledButtonDefaultMouseCursor
extends
MaterialStateProperty
<
MouseCursor
?>
with
Diagnosticable
{
_FilledButtonDefaultMouseCursor
(
this
.
enabledCursor
,
this
.
disabledCursor
);
final
MouseCursor
?
enabledCursor
;
final
MouseCursor
?
disabledCursor
;
@override
MouseCursor
?
resolve
(
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
disabled
))
{
return
disabledCursor
;
}
return
enabledCursor
;
}
}
class
_FilledButtonWithIcon
extends
FilledButton
{
_FilledButtonWithIcon
({
super
.
key
,
required
super
.
onPressed
,
super
.
onLongPress
,
super
.
onHover
,
super
.
onFocusChange
,
super
.
style
,
super
.
focusNode
,
bool
?
autofocus
,
Clip
?
clipBehavior
,
required
Widget
icon
,
required
Widget
label
,
})
:
assert
(
icon
!=
null
),
assert
(
label
!=
null
),
super
(
autofocus:
autofocus
??
false
,
clipBehavior:
clipBehavior
??
Clip
.
none
,
child:
_FilledButtonWithIconChild
(
icon:
icon
,
label:
label
)
);
_FilledButtonWithIcon
.
tonal
({
super
.
key
,
required
super
.
onPressed
,
super
.
onLongPress
,
super
.
onHover
,
super
.
onFocusChange
,
super
.
style
,
super
.
focusNode
,
bool
?
autofocus
,
Clip
?
clipBehavior
,
required
Widget
icon
,
required
Widget
label
,
})
:
assert
(
icon
!=
null
),
assert
(
label
!=
null
),
super
.
tonal
(
autofocus:
autofocus
??
false
,
clipBehavior:
clipBehavior
??
Clip
.
none
,
child:
_FilledButtonWithIconChild
(
icon:
icon
,
label:
label
)
);
@override
ButtonStyle
defaultStyleOf
(
BuildContext
context
)
{
final
EdgeInsetsGeometry
scaledPadding
=
ButtonStyleButton
.
scaledPadding
(
const
EdgeInsetsDirectional
.
fromSTEB
(
12
,
0
,
16
,
0
),
const
EdgeInsets
.
symmetric
(
horizontal:
8
),
const
EdgeInsetsDirectional
.
fromSTEB
(
8
,
0
,
4
,
0
),
MediaQuery
.
maybeOf
(
context
)?.
textScaleFactor
??
1
,
);
return
super
.
defaultStyleOf
(
context
).
copyWith
(
padding:
MaterialStatePropertyAll
<
EdgeInsetsGeometry
>(
scaledPadding
),
);
}
}
class
_FilledButtonWithIconChild
extends
StatelessWidget
{
const
_FilledButtonWithIconChild
({
required
this
.
label
,
required
this
.
icon
});
final
Widget
label
;
final
Widget
icon
;
@override
Widget
build
(
BuildContext
context
)
{
final
double
scale
=
MediaQuery
.
maybeOf
(
context
)?.
textScaleFactor
??
1
;
// Adjust the gap based on the text scale factor. Start at 8, and lerp
// to 4 based on how large the text is.
final
double
gap
=
scale
<=
1
?
8
:
lerpDouble
(
8
,
4
,
math
.
min
(
scale
-
1
,
1
))!;
return
Row
(
mainAxisSize:
MainAxisSize
.
min
,
children:
<
Widget
>[
icon
,
SizedBox
(
width:
gap
),
Flexible
(
child:
label
)],
);
}
}
// BEGIN GENERATED TOKEN PROPERTIES - FilledButton
// Do not edit by hand. The code between the "BEGIN GENERATED" and
// "END GENERATED" comments are generated from data in the Material
// Design token database by the script:
// dev/tools/gen_defaults/bin/gen_defaults.dart.
// Token database version: v0_101
class
_FilledButtonDefaultsM3
extends
ButtonStyle
{
_FilledButtonDefaultsM3
(
this
.
context
)
:
super
(
animationDuration:
kThemeChangeDuration
,
enableFeedback:
true
,
alignment:
Alignment
.
center
,
);
final
BuildContext
context
;
late
final
ColorScheme
_colors
=
Theme
.
of
(
context
).
colorScheme
;
@override
MaterialStateProperty
<
TextStyle
?>
get
textStyle
=>
MaterialStatePropertyAll
<
TextStyle
?>(
Theme
.
of
(
context
).
textTheme
.
labelLarge
);
@override
MaterialStateProperty
<
Color
?>?
get
backgroundColor
=>
MaterialStateProperty
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
disabled
))
{
return
_colors
.
onSurface
.
withOpacity
(
0.12
);
}
return
_colors
.
primary
;
});
@override
MaterialStateProperty
<
Color
?>?
get
foregroundColor
=>
MaterialStateProperty
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
disabled
))
{
return
_colors
.
onSurface
.
withOpacity
(
0.38
);
}
return
_colors
.
onPrimary
;
});
@override
MaterialStateProperty
<
Color
?>?
get
overlayColor
=>
MaterialStateProperty
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
hovered
))
{
return
_colors
.
onPrimary
.
withOpacity
(
0.08
);
}
if
(
states
.
contains
(
MaterialState
.
focused
))
{
return
_colors
.
onPrimary
.
withOpacity
(
0.12
);
}
if
(
states
.
contains
(
MaterialState
.
pressed
))
{
return
_colors
.
onPrimary
.
withOpacity
(
0.12
);
}
return
null
;
});
@override
MaterialStateProperty
<
Color
>?
get
shadowColor
=>
ButtonStyleButton
.
allOrNull
<
Color
>(
_colors
.
shadow
);
// No default surface tint color
@override
MaterialStateProperty
<
double
>?
get
elevation
=>
MaterialStateProperty
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
disabled
))
{
return
0.0
;
}
if
(
states
.
contains
(
MaterialState
.
hovered
))
{
return
1.0
;
}
if
(
states
.
contains
(
MaterialState
.
focused
))
{
return
0.0
;
}
if
(
states
.
contains
(
MaterialState
.
pressed
))
{
return
0.0
;
}
return
0.0
;
});
@override
MaterialStateProperty
<
EdgeInsetsGeometry
>?
get
padding
=>
ButtonStyleButton
.
allOrNull
<
EdgeInsetsGeometry
>(
_scaledPadding
(
context
));
@override
MaterialStateProperty
<
Size
>?
get
minimumSize
=>
ButtonStyleButton
.
allOrNull
<
Size
>(
const
Size
(
64.0
,
40.0
));
// No default fixedSize
@override
MaterialStateProperty
<
Size
>?
get
maximumSize
=>
ButtonStyleButton
.
allOrNull
<
Size
>(
Size
.
infinite
);
// No default side
@override
MaterialStateProperty
<
OutlinedBorder
>?
get
shape
=>
ButtonStyleButton
.
allOrNull
<
OutlinedBorder
>(
const
StadiumBorder
());
@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
;
}
// END GENERATED TOKEN PROPERTIES - FilledButton
// BEGIN GENERATED TOKEN PROPERTIES - FilledTonalButton
// Do not edit by hand. The code between the "BEGIN GENERATED" and
// "END GENERATED" comments are generated from data in the Material
// Design token database by the script:
// dev/tools/gen_defaults/bin/gen_defaults.dart.
// Token database version: v0_101
class
_FilledTonalButtonDefaultsM3
extends
ButtonStyle
{
_FilledTonalButtonDefaultsM3
(
this
.
context
)
:
super
(
animationDuration:
kThemeChangeDuration
,
enableFeedback:
true
,
alignment:
Alignment
.
center
,
);
final
BuildContext
context
;
late
final
ColorScheme
_colors
=
Theme
.
of
(
context
).
colorScheme
;
@override
MaterialStateProperty
<
TextStyle
?>
get
textStyle
=>
MaterialStatePropertyAll
<
TextStyle
?>(
Theme
.
of
(
context
).
textTheme
.
labelLarge
);
@override
MaterialStateProperty
<
Color
?>?
get
backgroundColor
=>
MaterialStateProperty
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
disabled
))
{
return
_colors
.
onSurface
.
withOpacity
(
0.12
);
}
return
_colors
.
secondaryContainer
;
});
@override
MaterialStateProperty
<
Color
?>?
get
foregroundColor
=>
MaterialStateProperty
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
disabled
))
{
return
_colors
.
onSurface
.
withOpacity
(
0.38
);
}
return
_colors
.
onSecondaryContainer
;
});
@override
MaterialStateProperty
<
Color
?>?
get
overlayColor
=>
MaterialStateProperty
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
hovered
))
{
return
_colors
.
onSecondaryContainer
.
withOpacity
(
0.08
);
}
if
(
states
.
contains
(
MaterialState
.
focused
))
{
return
_colors
.
onSecondaryContainer
.
withOpacity
(
0.12
);
}
if
(
states
.
contains
(
MaterialState
.
pressed
))
{
return
_colors
.
onSecondaryContainer
.
withOpacity
(
0.12
);
}
return
null
;
});
@override
MaterialStateProperty
<
Color
>?
get
shadowColor
=>
ButtonStyleButton
.
allOrNull
<
Color
>(
_colors
.
shadow
);
// No default surface tint color
@override
MaterialStateProperty
<
double
>?
get
elevation
=>
MaterialStateProperty
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
disabled
))
{
return
0.0
;
}
if
(
states
.
contains
(
MaterialState
.
hovered
))
{
return
1.0
;
}
if
(
states
.
contains
(
MaterialState
.
focused
))
{
return
0.0
;
}
if
(
states
.
contains
(
MaterialState
.
pressed
))
{
return
0.0
;
}
return
0.0
;
});
@override
MaterialStateProperty
<
EdgeInsetsGeometry
>?
get
padding
=>
ButtonStyleButton
.
allOrNull
<
EdgeInsetsGeometry
>(
_scaledPadding
(
context
));
@override
MaterialStateProperty
<
Size
>?
get
minimumSize
=>
ButtonStyleButton
.
allOrNull
<
Size
>(
const
Size
(
64.0
,
40.0
));
// No default fixedSize
@override
MaterialStateProperty
<
Size
>?
get
maximumSize
=>
ButtonStyleButton
.
allOrNull
<
Size
>(
Size
.
infinite
);
// No default side
@override
MaterialStateProperty
<
OutlinedBorder
>?
get
shape
=>
ButtonStyleButton
.
allOrNull
<
OutlinedBorder
>(
const
StadiumBorder
());
@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
;
}
// END GENERATED TOKEN PROPERTIES - FilledTonalButton
packages/flutter/lib/src/material/filled_button_theme.dart
0 → 100644
View file @
5454bab6
// 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
'package:flutter/foundation.dart'
;
import
'package:flutter/widgets.dart'
;
import
'button_style.dart'
;
import
'theme.dart'
;
// Examples can assume:
// late BuildContext context;
/// A [ButtonStyle] that overrides the default appearance of
/// [FilledButton]s when it's used with [FilledButtonTheme] or with the
/// overall [Theme]'s [ThemeData.filledButtonTheme].
///
/// The [style]'s properties override [FilledButton]'s default style,
/// i.e. the [ButtonStyle] returned by [FilledButton.defaultStyleOf]. Only
/// the style's non-null property values or resolved non-null
/// [MaterialStateProperty] values are used.
///
/// See also:
///
/// * [FilledButtonTheme], the theme which is configured with this class.
/// * [FilledButton.defaultStyleOf], which returns the default [ButtonStyle]
/// for text buttons.
/// * [FilledButton.styleFrom], which converts simple values into a
/// [ButtonStyle] that's consistent with [FilledButton]'s defaults.
/// * [MaterialStateProperty.resolve], "resolve" a material state property
/// to a simple value based on a set of [MaterialState]s.
/// * [ThemeData.filledButtonTheme], which can be used to override the default
/// [ButtonStyle] for [FilledButton]s below the overall [Theme].
@immutable
class
FilledButtonThemeData
with
Diagnosticable
{
/// Creates an [FilledButtonThemeData].
///
/// The [style] may be null.
const
FilledButtonThemeData
({
this
.
style
});
/// Overrides for [FilledButton]'s default style.
///
/// Non-null properties or non-null resolved [MaterialStateProperty]
/// values override the [ButtonStyle] returned by
/// [FilledButton.defaultStyleOf].
///
/// If [style] is null, then this theme doesn't override anything.
final
ButtonStyle
?
style
;
/// Linearly interpolate between two filled button themes.
static
FilledButtonThemeData
?
lerp
(
FilledButtonThemeData
?
a
,
FilledButtonThemeData
?
b
,
double
t
)
{
assert
(
t
!=
null
);
if
(
a
==
null
&&
b
==
null
)
{
return
null
;
}
return
FilledButtonThemeData
(
style:
ButtonStyle
.
lerp
(
a
?.
style
,
b
?.
style
,
t
),
);
}
@override
int
get
hashCode
=>
style
.
hashCode
;
@override
bool
operator
==(
Object
other
)
{
if
(
identical
(
this
,
other
))
{
return
true
;
}
if
(
other
.
runtimeType
!=
runtimeType
)
{
return
false
;
}
return
other
is
FilledButtonThemeData
&&
other
.
style
==
style
;
}
@override
void
debugFillProperties
(
DiagnosticPropertiesBuilder
properties
)
{
super
.
debugFillProperties
(
properties
);
properties
.
add
(
DiagnosticsProperty
<
ButtonStyle
>(
'style'
,
style
,
defaultValue:
null
));
}
}
/// Overrides the default [ButtonStyle] of its [FilledButton] descendants.
///
/// See also:
///
/// * [FilledButtonThemeData], which is used to configure this theme.
/// * [FilledButton.defaultStyleOf], which returns the default [ButtonStyle]
/// for filled buttons.
/// * [FilledButton.styleFrom], which converts simple values into a
/// [ButtonStyle] that's consistent with [FilledButton]'s defaults.
/// * [ThemeData.filledButtonTheme], which can be used to override the default
/// [ButtonStyle] for [FilledButton]s below the overall [Theme].
class
FilledButtonTheme
extends
InheritedTheme
{
/// Create a [FilledButtonTheme].
///
/// The [data] parameter must not be null.
const
FilledButtonTheme
({
super
.
key
,
required
this
.
data
,
required
super
.
child
,
})
:
assert
(
data
!=
null
);
/// The configuration of this theme.
final
FilledButtonThemeData
data
;
/// The closest instance of this class that encloses the given context.
///
/// If there is no enclosing [FilledButtonTheme] widget, then
/// [ThemeData.filledButtonTheme] is used.
///
/// Typical usage is as follows:
///
/// ```dart
/// FilledButtonThemeData theme = FilledButtonTheme.of(context);
/// ```
static
FilledButtonThemeData
of
(
BuildContext
context
)
{
final
FilledButtonTheme
?
buttonTheme
=
context
.
dependOnInheritedWidgetOfExactType
<
FilledButtonTheme
>();
return
buttonTheme
?.
data
??
Theme
.
of
(
context
).
filledButtonTheme
;
}
@override
Widget
wrap
(
BuildContext
context
,
Widget
child
)
{
return
FilledButtonTheme
(
data:
data
,
child:
child
);
}
@override
bool
updateShouldNotify
(
FilledButtonTheme
oldWidget
)
=>
data
!=
oldWidget
.
data
;
}
packages/flutter/lib/src/material/outlined_button.dart
View file @
5454bab6
...
...
@@ -59,8 +59,10 @@ import 'theme_data.dart';
///
/// See also:
///
/// * [ElevatedButton], a filled Material Design button with a shadow.
/// * [TextButton], a Material Design button without a shadow.
/// * [ElevatedButton], a filled button whose material elevates when pressed.
/// * [FilledButton], a filled button that doesn't elevate when pressed.
/// * [FilledButton.tonal], a filled button variant that uses a secondary fill color.
/// * [TextButton], a button with no outline or fill color.
/// * <https://material.io/design/components/buttons.html>
/// * <https://m3.material.io/components/buttons>
class
OutlinedButton
extends
ButtonStyleButton
{
...
...
packages/flutter/lib/src/material/text_button.dart
View file @
5454bab6
...
...
@@ -66,8 +66,10 @@ import 'theme_data.dart';
///
/// See also:
///
/// * [OutlinedButton], a [TextButton] with a border outline.
/// * [ElevatedButton], a filled button whose material elevates when pressed.
/// * [FilledButton], a filled button that doesn't elevate when pressed.
/// * [FilledButton.tonal], a filled button variant that uses a secondary fill color.
/// * [OutlinedButton], a button with an outlined border and no fill color.
/// * <https://material.io/design/components/buttons.html>
/// * <https://m3.material.io/components/buttons>
class
TextButton
extends
ButtonStyleButton
{
...
...
packages/flutter/lib/src/material/theme_data.dart
View file @
5454bab6
...
...
@@ -26,6 +26,7 @@ import 'divider_theme.dart';
import
'drawer_theme.dart'
;
import
'elevated_button_theme.dart'
;
import
'expansion_tile_theme.dart'
;
import
'filled_button_theme.dart'
;
import
'floating_action_button_theme.dart'
;
import
'icon_button_theme.dart'
;
import
'ink_ripple.dart'
;
...
...
@@ -343,6 +344,7 @@ class ThemeData with Diagnosticable {
DrawerThemeData
?
drawerTheme
,
ElevatedButtonThemeData
?
elevatedButtonTheme
,
ExpansionTileThemeData
?
expansionTileTheme
,
FilledButtonThemeData
?
filledButtonTheme
,
FloatingActionButtonThemeData
?
floatingActionButtonTheme
,
IconButtonThemeData
?
iconButtonTheme
,
ListTileThemeData
?
listTileTheme
,
...
...
@@ -564,6 +566,7 @@ class ThemeData with Diagnosticable {
dividerTheme
??=
const
DividerThemeData
();
drawerTheme
??=
const
DrawerThemeData
();
elevatedButtonTheme
??=
const
ElevatedButtonThemeData
();
filledButtonTheme
??=
const
FilledButtonThemeData
();
floatingActionButtonTheme
??=
const
FloatingActionButtonThemeData
();
iconButtonTheme
??=
const
IconButtonThemeData
();
listTileTheme
??=
const
ListTileThemeData
();
...
...
@@ -655,6 +658,7 @@ class ThemeData with Diagnosticable {
drawerTheme:
drawerTheme
,
elevatedButtonTheme:
elevatedButtonTheme
,
expansionTileTheme:
expansionTileTheme
,
filledButtonTheme:
filledButtonTheme
,
floatingActionButtonTheme:
floatingActionButtonTheme
,
iconButtonTheme:
iconButtonTheme
,
listTileTheme:
listTileTheme
,
...
...
@@ -761,6 +765,7 @@ class ThemeData with Diagnosticable {
required
this
.
drawerTheme
,
required
this
.
elevatedButtonTheme
,
required
this
.
expansionTileTheme
,
required
this
.
filledButtonTheme
,
required
this
.
floatingActionButtonTheme
,
required
this
.
iconButtonTheme
,
required
this
.
listTileTheme
,
...
...
@@ -909,6 +914,7 @@ class ThemeData with Diagnosticable {
assert
(
drawerTheme
!=
null
),
assert
(
elevatedButtonTheme
!=
null
),
assert
(
expansionTileTheme
!=
null
),
assert
(
filledButtonTheme
!=
null
),
assert
(
floatingActionButtonTheme
!=
null
),
assert
(
iconButtonTheme
!=
null
),
assert
(
listTileTheme
!=
null
),
...
...
@@ -1211,7 +1217,7 @@ class ThemeData with Diagnosticable {
/// * Typography: `typography` (see table above)
///
/// ### Components
/// * Common buttons: [
TextButton], [OutlinedButton], [Elevated
Button]
/// * Common buttons: [
ElevatedButton], [FilledButton], [OutlinedButton], [Text
Button]
/// * FAB: [FloatingActionButton]
/// * Extended FAB: [FloatingActionButton.extended]
/// * Cards: [Card]
...
...
@@ -1464,6 +1470,10 @@ class ThemeData with Diagnosticable {
/// A theme for customizing the visual properties of [ExpansionTile]s.
final
ExpansionTileThemeData
expansionTileTheme
;
/// A theme for customizing the appearance and internal layout of
/// [FilledButton]s.
final
FilledButtonThemeData
filledButtonTheme
;
/// A theme for customizing the shape, elevation, and color of a
/// [FloatingActionButton].
final
FloatingActionButtonThemeData
floatingActionButtonTheme
;
...
...
@@ -1739,6 +1749,7 @@ class ThemeData with Diagnosticable {
DrawerThemeData
?
drawerTheme
,
ElevatedButtonThemeData
?
elevatedButtonTheme
,
ExpansionTileThemeData
?
expansionTileTheme
,
FilledButtonThemeData
?
filledButtonTheme
,
FloatingActionButtonThemeData
?
floatingActionButtonTheme
,
IconButtonThemeData
?
iconButtonTheme
,
ListTileThemeData
?
listTileTheme
,
...
...
@@ -1884,6 +1895,7 @@ class ThemeData with Diagnosticable {
drawerTheme:
drawerTheme
??
this
.
drawerTheme
,
elevatedButtonTheme:
elevatedButtonTheme
??
this
.
elevatedButtonTheme
,
expansionTileTheme:
expansionTileTheme
??
this
.
expansionTileTheme
,
filledButtonTheme:
filledButtonTheme
??
this
.
filledButtonTheme
,
floatingActionButtonTheme:
floatingActionButtonTheme
??
this
.
floatingActionButtonTheme
,
iconButtonTheme:
iconButtonTheme
??
this
.
iconButtonTheme
,
listTileTheme:
listTileTheme
??
this
.
listTileTheme
,
...
...
@@ -2083,6 +2095,7 @@ class ThemeData with Diagnosticable {
drawerTheme:
DrawerThemeData
.
lerp
(
a
.
drawerTheme
,
b
.
drawerTheme
,
t
)!,
elevatedButtonTheme:
ElevatedButtonThemeData
.
lerp
(
a
.
elevatedButtonTheme
,
b
.
elevatedButtonTheme
,
t
)!,
expansionTileTheme:
ExpansionTileThemeData
.
lerp
(
a
.
expansionTileTheme
,
b
.
expansionTileTheme
,
t
)!,
filledButtonTheme:
FilledButtonThemeData
.
lerp
(
a
.
filledButtonTheme
,
b
.
filledButtonTheme
,
t
)!,
floatingActionButtonTheme:
FloatingActionButtonThemeData
.
lerp
(
a
.
floatingActionButtonTheme
,
b
.
floatingActionButtonTheme
,
t
)!,
iconButtonTheme:
IconButtonThemeData
.
lerp
(
a
.
iconButtonTheme
,
b
.
iconButtonTheme
,
t
)!,
listTileTheme:
ListTileThemeData
.
lerp
(
a
.
listTileTheme
,
b
.
listTileTheme
,
t
)!,
...
...
@@ -2184,6 +2197,7 @@ class ThemeData with Diagnosticable {
other
.
drawerTheme
==
drawerTheme
&&
other
.
elevatedButtonTheme
==
elevatedButtonTheme
&&
other
.
expansionTileTheme
==
expansionTileTheme
&&
other
.
filledButtonTheme
==
filledButtonTheme
&&
other
.
floatingActionButtonTheme
==
floatingActionButtonTheme
&&
other
.
iconButtonTheme
==
iconButtonTheme
&&
other
.
listTileTheme
==
listTileTheme
&&
...
...
@@ -2282,6 +2296,7 @@ class ThemeData with Diagnosticable {
drawerTheme
,
elevatedButtonTheme
,
expansionTileTheme
,
filledButtonTheme
,
floatingActionButtonTheme
,
iconButtonTheme
,
listTileTheme
,
...
...
@@ -2382,6 +2397,7 @@ class ThemeData with Diagnosticable {
properties
.
add
(
DiagnosticsProperty
<
DrawerThemeData
>(
'drawerTheme'
,
drawerTheme
,
defaultValue:
defaultData
.
drawerTheme
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
ElevatedButtonThemeData
>(
'elevatedButtonTheme'
,
elevatedButtonTheme
,
defaultValue:
defaultData
.
elevatedButtonTheme
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
ExpansionTileThemeData
>(
'expansionTileTheme'
,
expansionTileTheme
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
FilledButtonThemeData
>(
'filledButtonTheme'
,
filledButtonTheme
,
defaultValue:
defaultData
.
filledButtonTheme
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
FloatingActionButtonThemeData
>(
'floatingActionButtonTheme'
,
floatingActionButtonTheme
,
defaultValue:
defaultData
.
floatingActionButtonTheme
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
IconButtonThemeData
>(
'iconButtonTheme'
,
iconButtonTheme
,
defaultValue:
defaultData
.
iconButtonTheme
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
ListTileThemeData
>(
'listTileTheme'
,
listTileTheme
,
defaultValue:
defaultData
.
listTileTheme
,
level:
DiagnosticLevel
.
debug
));
...
...
packages/flutter/test/material/filled_button_test.dart
0 → 100644
View file @
5454bab6
// 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
'package:flutter/foundation.dart'
;
import
'package:flutter/gestures.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'../rendering/mock_canvas.dart'
;
import
'../widgets/semantics_tester.dart'
;
void
main
(
)
{
testWidgets
(
'FilledButton, FilledButton.icon defaults'
,
(
WidgetTester
tester
)
async
{
const
ColorScheme
colorScheme
=
ColorScheme
.
light
();
final
ThemeData
theme
=
ThemeData
.
from
(
colorScheme:
colorScheme
);
// Enabled FilledButton
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
theme
,
home:
Center
(
child:
FilledButton
(
onPressed:
()
{
},
child:
const
Text
(
'button'
),
),
),
),
);
final
Finder
buttonMaterial
=
find
.
descendant
(
of:
find
.
byType
(
FilledButton
),
matching:
find
.
byType
(
Material
),
);
Material
material
=
tester
.
widget
<
Material
>(
buttonMaterial
);
expect
(
material
.
animationDuration
,
const
Duration
(
milliseconds:
200
));
expect
(
material
.
borderOnForeground
,
true
);
expect
(
material
.
borderRadius
,
null
);
expect
(
material
.
clipBehavior
,
Clip
.
none
);
expect
(
material
.
color
,
colorScheme
.
primary
);
expect
(
material
.
elevation
,
0
);
expect
(
material
.
shadowColor
,
const
Color
(
0xff000000
));
expect
(
material
.
shape
,
const
StadiumBorder
());
expect
(
material
.
textStyle
!.
color
,
colorScheme
.
onPrimary
);
expect
(
material
.
textStyle
!.
fontFamily
,
'Roboto'
);
expect
(
material
.
textStyle
!.
fontSize
,
14
);
expect
(
material
.
textStyle
!.
fontWeight
,
FontWeight
.
w500
);
expect
(
material
.
type
,
MaterialType
.
button
);
final
Align
align
=
tester
.
firstWidget
<
Align
>(
find
.
ancestor
(
of:
find
.
text
(
'button'
),
matching:
find
.
byType
(
Align
)));
expect
(
align
.
alignment
,
Alignment
.
center
);
final
Offset
center
=
tester
.
getCenter
(
find
.
byType
(
FilledButton
));
await
tester
.
startGesture
(
center
);
await
tester
.
pump
();
// start the splash animation
await
tester
.
pump
(
const
Duration
(
milliseconds:
100
));
// splash is underway
// Enabled FilledButton.icon
final
Key
iconButtonKey
=
UniqueKey
();
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
theme
,
home:
Center
(
child:
FilledButton
.
icon
(
key:
iconButtonKey
,
onPressed:
()
{
},
icon:
const
Icon
(
Icons
.
add
),
label:
const
Text
(
'label'
),
),
),
),
);
final
Finder
iconButtonMaterial
=
find
.
descendant
(
of:
find
.
byKey
(
iconButtonKey
),
matching:
find
.
byType
(
Material
),
);
material
=
tester
.
widget
<
Material
>(
iconButtonMaterial
);
expect
(
material
.
animationDuration
,
const
Duration
(
milliseconds:
200
));
expect
(
material
.
borderOnForeground
,
true
);
expect
(
material
.
borderRadius
,
null
);
expect
(
material
.
clipBehavior
,
Clip
.
none
);
expect
(
material
.
color
,
colorScheme
.
primary
);
expect
(
material
.
elevation
,
0
);
expect
(
material
.
shadowColor
,
const
Color
(
0xff000000
));
expect
(
material
.
shape
,
const
StadiumBorder
());
expect
(
material
.
textStyle
!.
color
,
colorScheme
.
onPrimary
);
expect
(
material
.
textStyle
!.
fontFamily
,
'Roboto'
);
expect
(
material
.
textStyle
!.
fontSize
,
14
);
expect
(
material
.
textStyle
!.
fontWeight
,
FontWeight
.
w500
);
expect
(
material
.
type
,
MaterialType
.
button
);
// Disabled FilledButton
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
theme
,
home:
const
Center
(
child:
FilledButton
(
onPressed:
null
,
child:
Text
(
'button'
),
),
),
),
);
// Finish the elevation animation, final background color change.
await
tester
.
pumpAndSettle
();
material
=
tester
.
widget
<
Material
>(
buttonMaterial
);
expect
(
material
.
animationDuration
,
const
Duration
(
milliseconds:
200
));
expect
(
material
.
borderOnForeground
,
true
);
expect
(
material
.
borderRadius
,
null
);
expect
(
material
.
clipBehavior
,
Clip
.
none
);
expect
(
material
.
color
,
colorScheme
.
onSurface
.
withOpacity
(
0.12
));
expect
(
material
.
elevation
,
0.0
);
expect
(
material
.
shadowColor
,
const
Color
(
0xff000000
));
expect
(
material
.
shape
,
const
StadiumBorder
());
expect
(
material
.
textStyle
!.
color
,
colorScheme
.
onSurface
.
withOpacity
(
0.38
));
expect
(
material
.
textStyle
!.
fontFamily
,
'Roboto'
);
expect
(
material
.
textStyle
!.
fontSize
,
14
);
expect
(
material
.
textStyle
!.
fontWeight
,
FontWeight
.
w500
);
expect
(
material
.
type
,
MaterialType
.
button
);
});
testWidgets
(
'FilledButton.tonal, FilledButton.tonalIcon defaults'
,
(
WidgetTester
tester
)
async
{
const
ColorScheme
colorScheme
=
ColorScheme
.
light
();
final
ThemeData
theme
=
ThemeData
.
from
(
colorScheme:
colorScheme
);
// Enabled FilledButton
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
theme
,
home:
Center
(
child:
FilledButton
.
tonal
(
onPressed:
()
{
},
child:
const
Text
(
'button'
),
),
),
),
);
final
Finder
buttonMaterial
=
find
.
descendant
(
of:
find
.
byType
(
FilledButton
),
matching:
find
.
byType
(
Material
),
);
Material
material
=
tester
.
widget
<
Material
>(
buttonMaterial
);
expect
(
material
.
animationDuration
,
const
Duration
(
milliseconds:
200
));
expect
(
material
.
borderOnForeground
,
true
);
expect
(
material
.
borderRadius
,
null
);
expect
(
material
.
clipBehavior
,
Clip
.
none
);
expect
(
material
.
color
,
colorScheme
.
secondaryContainer
);
expect
(
material
.
elevation
,
0
);
expect
(
material
.
shadowColor
,
const
Color
(
0xff000000
));
expect
(
material
.
shape
,
const
StadiumBorder
());
expect
(
material
.
textStyle
!.
color
,
colorScheme
.
onSecondaryContainer
);
expect
(
material
.
textStyle
!.
fontFamily
,
'Roboto'
);
expect
(
material
.
textStyle
!.
fontSize
,
14
);
expect
(
material
.
textStyle
!.
fontWeight
,
FontWeight
.
w500
);
expect
(
material
.
type
,
MaterialType
.
button
);
final
Align
align
=
tester
.
firstWidget
<
Align
>(
find
.
ancestor
(
of:
find
.
text
(
'button'
),
matching:
find
.
byType
(
Align
)));
expect
(
align
.
alignment
,
Alignment
.
center
);
final
Offset
center
=
tester
.
getCenter
(
find
.
byType
(
FilledButton
));
await
tester
.
startGesture
(
center
);
await
tester
.
pump
();
// start the splash animation
await
tester
.
pump
(
const
Duration
(
milliseconds:
100
));
// splash is underway
// Enabled FilledButton.tonalIcon
final
Key
iconButtonKey
=
UniqueKey
();
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
theme
,
home:
Center
(
child:
FilledButton
.
tonalIcon
(
key:
iconButtonKey
,
onPressed:
()
{
},
icon:
const
Icon
(
Icons
.
add
),
label:
const
Text
(
'label'
),
),
),
),
);
final
Finder
iconButtonMaterial
=
find
.
descendant
(
of:
find
.
byKey
(
iconButtonKey
),
matching:
find
.
byType
(
Material
),
);
material
=
tester
.
widget
<
Material
>(
iconButtonMaterial
);
expect
(
material
.
animationDuration
,
const
Duration
(
milliseconds:
200
));
expect
(
material
.
borderOnForeground
,
true
);
expect
(
material
.
borderRadius
,
null
);
expect
(
material
.
clipBehavior
,
Clip
.
none
);
expect
(
material
.
color
,
colorScheme
.
secondaryContainer
);
expect
(
material
.
elevation
,
0
);
expect
(
material
.
shadowColor
,
const
Color
(
0xff000000
));
expect
(
material
.
shape
,
const
StadiumBorder
());
expect
(
material
.
textStyle
!.
color
,
colorScheme
.
onSecondaryContainer
);
expect
(
material
.
textStyle
!.
fontFamily
,
'Roboto'
);
expect
(
material
.
textStyle
!.
fontSize
,
14
);
expect
(
material
.
textStyle
!.
fontWeight
,
FontWeight
.
w500
);
expect
(
material
.
type
,
MaterialType
.
button
);
// Disabled FilledButton
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
theme
,
home:
const
Center
(
child:
FilledButton
.
tonal
(
onPressed:
null
,
child:
Text
(
'button'
),
),
),
),
);
// Finish the elevation animation, final background color change.
await
tester
.
pumpAndSettle
();
material
=
tester
.
widget
<
Material
>(
buttonMaterial
);
expect
(
material
.
animationDuration
,
const
Duration
(
milliseconds:
200
));
expect
(
material
.
borderOnForeground
,
true
);
expect
(
material
.
borderRadius
,
null
);
expect
(
material
.
clipBehavior
,
Clip
.
none
);
expect
(
material
.
color
,
colorScheme
.
onSurface
.
withOpacity
(
0.12
));
expect
(
material
.
elevation
,
0.0
);
expect
(
material
.
shadowColor
,
const
Color
(
0xff000000
));
expect
(
material
.
shape
,
const
StadiumBorder
());
expect
(
material
.
textStyle
!.
color
,
colorScheme
.
onSurface
.
withOpacity
(
0.38
));
expect
(
material
.
textStyle
!.
fontFamily
,
'Roboto'
);
expect
(
material
.
textStyle
!.
fontSize
,
14
);
expect
(
material
.
textStyle
!.
fontWeight
,
FontWeight
.
w500
);
expect
(
material
.
type
,
MaterialType
.
button
);
});
testWidgets
(
'Default FilledButton meets a11y contrast guidelines'
,
(
WidgetTester
tester
)
async
{
final
FocusNode
focusNode
=
FocusNode
();
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
ThemeData
.
from
(
colorScheme:
const
ColorScheme
.
light
()),
home:
Scaffold
(
body:
Center
(
child:
FilledButton
(
onPressed:
()
{
},
focusNode:
focusNode
,
child:
const
Text
(
'FilledButton'
),
),
),
),
),
);
// Default, not disabled.
await
expectLater
(
tester
,
meetsGuideline
(
textContrastGuideline
));
// Focused.
focusNode
.
requestFocus
();
await
tester
.
pumpAndSettle
();
await
expectLater
(
tester
,
meetsGuideline
(
textContrastGuideline
));
// Hovered.
final
Offset
center
=
tester
.
getCenter
(
find
.
byType
(
FilledButton
));
final
TestGesture
gesture
=
await
tester
.
createGesture
(
kind:
PointerDeviceKind
.
mouse
,
);
await
gesture
.
addPointer
();
await
gesture
.
moveTo
(
center
);
await
tester
.
pumpAndSettle
();
await
expectLater
(
tester
,
meetsGuideline
(
textContrastGuideline
));
},
skip:
isBrowser
,
// https://github.com/flutter/flutter/issues/44115
);
testWidgets
(
'FilledButton uses stateful color for text color in different states'
,
(
WidgetTester
tester
)
async
{
final
FocusNode
focusNode
=
FocusNode
();
const
Color
pressedColor
=
Color
(
0x00000001
);
const
Color
hoverColor
=
Color
(
0x00000002
);
const
Color
focusedColor
=
Color
(
0x00000003
);
const
Color
defaultColor
=
Color
(
0x00000004
);
Color
getTextColor
(
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
pressed
))
{
return
pressedColor
;
}
if
(
states
.
contains
(
MaterialState
.
hovered
))
{
return
hoverColor
;
}
if
(
states
.
contains
(
MaterialState
.
focused
))
{
return
focusedColor
;
}
return
defaultColor
;
}
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Scaffold
(
body:
Center
(
child:
FilledButtonTheme
(
data:
FilledButtonThemeData
(
style:
ButtonStyle
(
foregroundColor:
MaterialStateProperty
.
resolveWith
<
Color
>(
getTextColor
),
),
),
child:
Builder
(
builder:
(
BuildContext
context
)
{
return
FilledButton
(
onPressed:
()
{},
focusNode:
focusNode
,
child:
const
Text
(
'FilledButton'
),
);
},
),
),
),
),
),
);
Color
textColor
()
{
return
tester
.
renderObject
<
RenderParagraph
>(
find
.
text
(
'FilledButton'
)).
text
.
style
!.
color
!;
}
// Default, not disabled.
expect
(
textColor
(),
equals
(
defaultColor
));
// Focused.
focusNode
.
requestFocus
();
await
tester
.
pumpAndSettle
();
expect
(
textColor
(),
focusedColor
);
// Hovered.
final
Offset
center
=
tester
.
getCenter
(
find
.
byType
(
FilledButton
));
final
TestGesture
gesture
=
await
tester
.
createGesture
(
kind:
PointerDeviceKind
.
mouse
,
);
await
gesture
.
addPointer
();
await
gesture
.
moveTo
(
center
);
await
tester
.
pumpAndSettle
();
expect
(
textColor
(),
hoverColor
);
// Highlighted (pressed).
await
gesture
.
down
(
center
);
await
tester
.
pump
();
// Start the splash and highlight animations.
await
tester
.
pump
(
const
Duration
(
milliseconds:
800
));
// Wait for splash and highlight to be well under way.
expect
(
textColor
(),
pressedColor
);
});
testWidgets
(
'FilledButton uses stateful color for icon color in different states'
,
(
WidgetTester
tester
)
async
{
final
FocusNode
focusNode
=
FocusNode
();
final
Key
buttonKey
=
UniqueKey
();
const
Color
pressedColor
=
Color
(
0x00000001
);
const
Color
hoverColor
=
Color
(
0x00000002
);
const
Color
focusedColor
=
Color
(
0x00000003
);
const
Color
defaultColor
=
Color
(
0x00000004
);
Color
getTextColor
(
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
pressed
))
{
return
pressedColor
;
}
if
(
states
.
contains
(
MaterialState
.
hovered
))
{
return
hoverColor
;
}
if
(
states
.
contains
(
MaterialState
.
focused
))
{
return
focusedColor
;
}
return
defaultColor
;
}
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Scaffold
(
body:
Center
(
child:
FilledButtonTheme
(
data:
FilledButtonThemeData
(
style:
ButtonStyle
(
foregroundColor:
MaterialStateProperty
.
resolveWith
<
Color
>(
getTextColor
),
),
),
child:
Builder
(
builder:
(
BuildContext
context
)
{
return
FilledButton
.
icon
(
key:
buttonKey
,
icon:
const
Icon
(
Icons
.
add
),
label:
const
Text
(
'FilledButton'
),
onPressed:
()
{},
focusNode:
focusNode
,
);
},
),
),
),
),
),
);
Color
iconColor
()
=>
_iconStyle
(
tester
,
Icons
.
add
).
color
!;
// Default, not disabled.
expect
(
iconColor
(),
equals
(
defaultColor
));
// Focused.
focusNode
.
requestFocus
();
await
tester
.
pumpAndSettle
();
expect
(
iconColor
(),
focusedColor
);
// Hovered.
final
Offset
center
=
tester
.
getCenter
(
find
.
byKey
(
buttonKey
));
final
TestGesture
gesture
=
await
tester
.
createGesture
(
kind:
PointerDeviceKind
.
mouse
,
);
await
gesture
.
addPointer
();
await
gesture
.
moveTo
(
center
);
await
tester
.
pumpAndSettle
();
expect
(
iconColor
(),
hoverColor
);
// Highlighted (pressed).
await
gesture
.
down
(
center
);
await
tester
.
pump
();
// Start the splash and highlight animations.
await
tester
.
pump
(
const
Duration
(
milliseconds:
800
));
// Wait for splash and highlight to be well under way.
expect
(
iconColor
(),
pressedColor
);
});
testWidgets
(
'FilledButton onPressed and onLongPress callbacks are correctly called when non-null'
,
(
WidgetTester
tester
)
async
{
bool
wasPressed
;
Finder
filledButton
;
Widget
buildFrame
({
VoidCallback
?
onPressed
,
VoidCallback
?
onLongPress
})
{
return
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
FilledButton
(
onPressed:
onPressed
,
onLongPress:
onLongPress
,
child:
const
Text
(
'button'
),
),
);
}
// onPressed not null, onLongPress null.
wasPressed
=
false
;
await
tester
.
pumpWidget
(
buildFrame
(
onPressed:
()
{
wasPressed
=
true
;
}),
);
filledButton
=
find
.
byType
(
FilledButton
);
expect
(
tester
.
widget
<
FilledButton
>(
filledButton
).
enabled
,
true
);
await
tester
.
tap
(
filledButton
);
expect
(
wasPressed
,
true
);
// onPressed null, onLongPress not null.
wasPressed
=
false
;
await
tester
.
pumpWidget
(
buildFrame
(
onLongPress:
()
{
wasPressed
=
true
;
}),
);
filledButton
=
find
.
byType
(
FilledButton
);
expect
(
tester
.
widget
<
FilledButton
>(
filledButton
).
enabled
,
true
);
await
tester
.
longPress
(
filledButton
);
expect
(
wasPressed
,
true
);
// onPressed null, onLongPress null.
await
tester
.
pumpWidget
(
buildFrame
(),
);
filledButton
=
find
.
byType
(
FilledButton
);
expect
(
tester
.
widget
<
FilledButton
>(
filledButton
).
enabled
,
false
);
});
testWidgets
(
'FilledButton onPressed and onLongPress callbacks are distinctly recognized'
,
(
WidgetTester
tester
)
async
{
bool
didPressButton
=
false
;
bool
didLongPressButton
=
false
;
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
FilledButton
(
onPressed:
()
{
didPressButton
=
true
;
},
onLongPress:
()
{
didLongPressButton
=
true
;
},
child:
const
Text
(
'button'
),
),
),
);
final
Finder
filledButton
=
find
.
byType
(
FilledButton
);
expect
(
tester
.
widget
<
FilledButton
>(
filledButton
).
enabled
,
true
);
expect
(
didPressButton
,
isFalse
);
await
tester
.
tap
(
filledButton
);
expect
(
didPressButton
,
isTrue
);
expect
(
didLongPressButton
,
isFalse
);
await
tester
.
longPress
(
filledButton
);
expect
(
didLongPressButton
,
isTrue
);
});
testWidgets
(
"FilledButton response doesn't hover when disabled"
,
(
WidgetTester
tester
)
async
{
FocusManager
.
instance
.
highlightStrategy
=
FocusHighlightStrategy
.
alwaysTouch
;
final
FocusNode
focusNode
=
FocusNode
(
debugLabel:
'FilledButton Focus'
);
final
GlobalKey
childKey
=
GlobalKey
();
bool
hovering
=
false
;
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
SizedBox
(
width:
100
,
height:
100
,
child:
FilledButton
(
autofocus:
true
,
onPressed:
()
{},
onLongPress:
()
{},
onHover:
(
bool
value
)
{
hovering
=
value
;
},
focusNode:
focusNode
,
child:
SizedBox
(
key:
childKey
),
),
),
),
);
await
tester
.
pumpAndSettle
();
expect
(
focusNode
.
hasPrimaryFocus
,
isTrue
);
final
TestGesture
gesture
=
await
tester
.
createGesture
(
kind:
PointerDeviceKind
.
mouse
);
await
gesture
.
addPointer
();
await
gesture
.
moveTo
(
tester
.
getCenter
(
find
.
byKey
(
childKey
)));
await
tester
.
pumpAndSettle
();
expect
(
hovering
,
isTrue
);
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
SizedBox
(
width:
100
,
height:
100
,
child:
FilledButton
(
focusNode:
focusNode
,
onHover:
(
bool
value
)
{
hovering
=
value
;
},
onPressed:
null
,
child:
SizedBox
(
key:
childKey
),
),
),
),
);
await
tester
.
pumpAndSettle
();
expect
(
focusNode
.
hasPrimaryFocus
,
isFalse
);
});
testWidgets
(
'disabled and hovered FilledButton responds to mouse-exit'
,
(
WidgetTester
tester
)
async
{
int
onHoverCount
=
0
;
late
bool
hover
;
Widget
buildFrame
({
required
bool
enabled
})
{
return
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Center
(
child:
SizedBox
(
width:
100
,
height:
100
,
child:
FilledButton
(
onPressed:
enabled
?
()
{
}
:
null
,
onHover:
(
bool
value
)
{
onHoverCount
+=
1
;
hover
=
value
;
},
child:
const
Text
(
'FilledButton'
),
),
),
),
);
}
await
tester
.
pumpWidget
(
buildFrame
(
enabled:
true
));
final
TestGesture
gesture
=
await
tester
.
createGesture
(
kind:
PointerDeviceKind
.
mouse
);
await
gesture
.
addPointer
();
await
gesture
.
moveTo
(
tester
.
getCenter
(
find
.
byType
(
FilledButton
)));
await
tester
.
pumpAndSettle
();
expect
(
onHoverCount
,
1
);
expect
(
hover
,
true
);
await
tester
.
pumpWidget
(
buildFrame
(
enabled:
false
));
await
tester
.
pumpAndSettle
();
await
gesture
.
moveTo
(
Offset
.
zero
);
// Even though the FilledButton has been disabled, the mouse-exit still
// causes onHover(false) to be called.
expect
(
onHoverCount
,
2
);
expect
(
hover
,
false
);
await
gesture
.
moveTo
(
tester
.
getCenter
(
find
.
byType
(
FilledButton
)));
await
tester
.
pumpAndSettle
();
// We no longer see hover events because the FilledButton is disabled
// and it's no longer in the "hovering" state.
expect
(
onHoverCount
,
2
);
expect
(
hover
,
false
);
await
tester
.
pumpWidget
(
buildFrame
(
enabled:
true
));
await
tester
.
pumpAndSettle
();
// The FilledButton was enabled while it contained the mouse, however
// we do not call onHover() because it may call setState().
expect
(
onHoverCount
,
2
);
expect
(
hover
,
false
);
await
gesture
.
moveTo
(
tester
.
getCenter
(
find
.
byType
(
FilledButton
))
-
const
Offset
(
1
,
1
));
await
tester
.
pumpAndSettle
();
// Moving the mouse a little within the FilledButton doesn't change anything.
expect
(
onHoverCount
,
2
);
expect
(
hover
,
false
);
});
testWidgets
(
'Can set FilledButton focus and Can set unFocus.'
,
(
WidgetTester
tester
)
async
{
final
FocusNode
node
=
FocusNode
(
debugLabel:
'FilledButton Focus'
);
bool
gotFocus
=
false
;
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
FilledButton
(
focusNode:
node
,
onFocusChange:
(
bool
focused
)
=>
gotFocus
=
focused
,
onPressed:
()
{
},
child:
const
SizedBox
(),
),
),
);
node
.
requestFocus
();
await
tester
.
pump
();
expect
(
gotFocus
,
isTrue
);
expect
(
node
.
hasFocus
,
isTrue
);
node
.
unfocus
();
await
tester
.
pump
();
expect
(
gotFocus
,
isFalse
);
expect
(
node
.
hasFocus
,
isFalse
);
});
testWidgets
(
'When FilledButton disable, Can not set FilledButton focus.'
,
(
WidgetTester
tester
)
async
{
final
FocusNode
node
=
FocusNode
(
debugLabel:
'FilledButton Focus'
);
bool
gotFocus
=
false
;
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
FilledButton
(
focusNode:
node
,
onFocusChange:
(
bool
focused
)
=>
gotFocus
=
focused
,
onPressed:
null
,
child:
const
SizedBox
(),
),
),
);
node
.
requestFocus
();
await
tester
.
pump
();
expect
(
gotFocus
,
isFalse
);
expect
(
node
.
hasFocus
,
isFalse
);
});
testWidgets
(
'Does FilledButton work with hover'
,
(
WidgetTester
tester
)
async
{
const
Color
hoverColor
=
Color
(
0xff001122
);
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
FilledButton
(
style:
ButtonStyle
(
overlayColor:
MaterialStateProperty
.
resolveWith
<
Color
?>((
Set
<
MaterialState
>
states
)
{
return
states
.
contains
(
MaterialState
.
hovered
)
?
hoverColor
:
null
;
}),
),
onPressed:
()
{
},
child:
const
Text
(
'button'
),
),
),
);
final
TestGesture
gesture
=
await
tester
.
createGesture
(
kind:
PointerDeviceKind
.
mouse
);
await
gesture
.
addPointer
();
await
gesture
.
moveTo
(
tester
.
getCenter
(
find
.
byType
(
FilledButton
)));
await
tester
.
pumpAndSettle
();
final
RenderObject
inkFeatures
=
tester
.
allRenderObjects
.
firstWhere
((
RenderObject
object
)
=>
object
.
runtimeType
.
toString
()
==
'_RenderInkFeatures'
);
expect
(
inkFeatures
,
paints
..
rect
(
color:
hoverColor
));
});
testWidgets
(
'Does FilledButton work with focus'
,
(
WidgetTester
tester
)
async
{
const
Color
focusColor
=
Color
(
0xff001122
);
final
FocusNode
focusNode
=
FocusNode
(
debugLabel:
'FilledButton Node'
);
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
FilledButton
(
style:
ButtonStyle
(
overlayColor:
MaterialStateProperty
.
resolveWith
<
Color
?>((
Set
<
MaterialState
>
states
)
{
return
states
.
contains
(
MaterialState
.
focused
)
?
focusColor
:
null
;
}),
),
focusNode:
focusNode
,
onPressed:
()
{
},
child:
const
Text
(
'button'
),
),
),
);
FocusManager
.
instance
.
highlightStrategy
=
FocusHighlightStrategy
.
alwaysTraditional
;
focusNode
.
requestFocus
();
await
tester
.
pumpAndSettle
();
final
RenderObject
inkFeatures
=
tester
.
allRenderObjects
.
firstWhere
((
RenderObject
object
)
=>
object
.
runtimeType
.
toString
()
==
'_RenderInkFeatures'
);
expect
(
inkFeatures
,
paints
..
rect
(
color:
focusColor
));
});
testWidgets
(
'Does FilledButton work with autofocus'
,
(
WidgetTester
tester
)
async
{
const
Color
focusColor
=
Color
(
0xff001122
);
Color
?
getOverlayColor
(
Set
<
MaterialState
>
states
)
{
return
states
.
contains
(
MaterialState
.
focused
)
?
focusColor
:
null
;
}
final
FocusNode
focusNode
=
FocusNode
(
debugLabel:
'FilledButton Node'
);
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
FilledButton
(
autofocus:
true
,
style:
ButtonStyle
(
overlayColor:
MaterialStateProperty
.
resolveWith
<
Color
?>(
getOverlayColor
),
),
focusNode:
focusNode
,
onPressed:
()
{
},
child:
const
Text
(
'button'
),
),
),
);
FocusManager
.
instance
.
highlightStrategy
=
FocusHighlightStrategy
.
alwaysTraditional
;
await
tester
.
pumpAndSettle
();
final
RenderObject
inkFeatures
=
tester
.
allRenderObjects
.
firstWhere
((
RenderObject
object
)
=>
object
.
runtimeType
.
toString
()
==
'_RenderInkFeatures'
);
expect
(
inkFeatures
,
paints
..
rect
(
color:
focusColor
));
});
testWidgets
(
'Does FilledButton contribute semantics'
,
(
WidgetTester
tester
)
async
{
final
SemanticsTester
semantics
=
SemanticsTester
(
tester
);
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Center
(
child:
FilledButton
(
style:
const
ButtonStyle
(
// Specifying minimumSize to mimic the original minimumSize for
// RaisedButton so that the semantics tree's rect and transform
// match the original version of this test.
minimumSize:
MaterialStatePropertyAll
<
Size
>(
Size
(
88
,
36
)),
),
onPressed:
()
{
},
child:
const
Text
(
'ABC'
),
),
),
),
);
expect
(
semantics
,
hasSemantics
(
TestSemantics
.
root
(
children:
<
TestSemantics
>[
TestSemantics
.
rootChild
(
actions:
<
SemanticsAction
>[
SemanticsAction
.
tap
,
],
label:
'ABC'
,
rect:
const
Rect
.
fromLTRB
(
0.0
,
0.0
,
88.0
,
48.0
),
transform:
Matrix4
.
translationValues
(
356.0
,
276.0
,
0.0
),
flags:
<
SemanticsFlag
>[
SemanticsFlag
.
hasEnabledState
,
SemanticsFlag
.
isButton
,
SemanticsFlag
.
isEnabled
,
SemanticsFlag
.
isFocusable
,
],
),
],
),
ignoreId:
true
,
));
semantics
.
dispose
();
});
testWidgets
(
'FilledButton size is configurable by ThemeData.materialTapTargetSize'
,
(
WidgetTester
tester
)
async
{
const
ButtonStyle
style
=
ButtonStyle
(
// Specifying minimumSize to mimic the original minimumSize for
// RaisedButton so that the corresponding button size matches
// the original version of this test.
minimumSize:
MaterialStatePropertyAll
<
Size
>(
Size
(
88
,
36
)),
);
Widget
buildFrame
(
MaterialTapTargetSize
tapTargetSize
,
Key
key
)
{
return
Theme
(
data:
ThemeData
(
materialTapTargetSize:
tapTargetSize
),
child:
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Center
(
child:
FilledButton
(
key:
key
,
style:
style
,
child:
const
SizedBox
(
width:
50.0
,
height:
8.0
),
onPressed:
()
{
},
),
),
),
);
}
final
Key
key1
=
UniqueKey
();
await
tester
.
pumpWidget
(
buildFrame
(
MaterialTapTargetSize
.
padded
,
key1
));
expect
(
tester
.
getSize
(
find
.
byKey
(
key1
)),
const
Size
(
88.0
,
48.0
));
final
Key
key2
=
UniqueKey
();
await
tester
.
pumpWidget
(
buildFrame
(
MaterialTapTargetSize
.
shrinkWrap
,
key2
));
expect
(
tester
.
getSize
(
find
.
byKey
(
key2
)),
const
Size
(
88.0
,
36.0
));
});
testWidgets
(
'FilledButton has no clip by default'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
FilledButton
(
onPressed:
()
{
/* to make sure the button is enabled */
},
child:
const
Text
(
'button'
),
),
),
);
expect
(
tester
.
renderObject
(
find
.
byType
(
FilledButton
)),
paintsExactlyCountTimes
(
#clipPath
,
0
),
);
});
testWidgets
(
'FilledButton responds to density changes.'
,
(
WidgetTester
tester
)
async
{
const
Key
key
=
Key
(
'test'
);
const
Key
childKey
=
Key
(
'test child'
);
Future
<
void
>
buildTest
(
VisualDensity
visualDensity
,
{
bool
useText
=
false
})
async
{
return
tester
.
pumpWidget
(
MaterialApp
(
// Test was setup using fonts from Material 2, so make sure we always
// test against englishLike2014.
theme:
ThemeData
(
textTheme:
Typography
.
englishLike2014
),
home:
Directionality
(
textDirection:
TextDirection
.
rtl
,
child:
Center
(
child:
FilledButton
(
style:
ButtonStyle
(
visualDensity:
visualDensity
,
// Specifying minimumSize to mimic the original minimumSize for
// RaisedButton so that the corresponding button size matches
// the original version of this test.
minimumSize:
const
MaterialStatePropertyAll
<
Size
>(
Size
(
88
,
36
)),
),
key:
key
,
onPressed:
()
{},
child:
useText
?
const
Text
(
'Text'
,
key:
childKey
)
:
Container
(
key:
childKey
,
width:
100
,
height:
100
,
color:
const
Color
(
0xffff0000
)),
),
),
),
),
);
}
await
buildTest
(
VisualDensity
.
standard
);
final
RenderBox
box
=
tester
.
renderObject
(
find
.
byKey
(
key
));
Rect
childRect
=
tester
.
getRect
(
find
.
byKey
(
childKey
));
await
tester
.
pumpAndSettle
();
expect
(
box
.
size
,
equals
(
const
Size
(
132
,
100
)));
expect
(
childRect
,
equals
(
const
Rect
.
fromLTRB
(
350
,
250
,
450
,
350
)));
await
buildTest
(
const
VisualDensity
(
horizontal:
3.0
,
vertical:
3.0
));
await
tester
.
pumpAndSettle
();
childRect
=
tester
.
getRect
(
find
.
byKey
(
childKey
));
expect
(
box
.
size
,
equals
(
const
Size
(
156
,
124
)));
expect
(
childRect
,
equals
(
const
Rect
.
fromLTRB
(
350
,
250
,
450
,
350
)));
await
buildTest
(
const
VisualDensity
(
horizontal:
-
3.0
,
vertical:
-
3.0
));
await
tester
.
pumpAndSettle
();
childRect
=
tester
.
getRect
(
find
.
byKey
(
childKey
));
expect
(
box
.
size
,
equals
(
const
Size
(
132
,
100
)));
expect
(
childRect
,
equals
(
const
Rect
.
fromLTRB
(
350
,
250
,
450
,
350
)));
await
buildTest
(
VisualDensity
.
standard
,
useText:
true
);
await
tester
.
pumpAndSettle
();
childRect
=
tester
.
getRect
(
find
.
byKey
(
childKey
));
expect
(
box
.
size
,
equals
(
const
Size
(
88
,
48
)));
expect
(
childRect
,
equals
(
const
Rect
.
fromLTRB
(
372.0
,
293.0
,
428.0
,
307.0
)));
await
buildTest
(
const
VisualDensity
(
horizontal:
3.0
,
vertical:
3.0
),
useText:
true
);
await
tester
.
pumpAndSettle
();
childRect
=
tester
.
getRect
(
find
.
byKey
(
childKey
));
expect
(
box
.
size
,
equals
(
const
Size
(
112
,
60
)));
expect
(
childRect
,
equals
(
const
Rect
.
fromLTRB
(
372.0
,
293.0
,
428.0
,
307.0
)));
await
buildTest
(
const
VisualDensity
(
horizontal:
-
3.0
,
vertical:
-
3.0
),
useText:
true
);
await
tester
.
pumpAndSettle
();
childRect
=
tester
.
getRect
(
find
.
byKey
(
childKey
));
expect
(
box
.
size
,
equals
(
const
Size
(
88
,
36
)));
expect
(
childRect
,
equals
(
const
Rect
.
fromLTRB
(
372.0
,
293.0
,
428.0
,
307.0
)));
});
testWidgets
(
'FilledButton.icon responds to applied padding'
,
(
WidgetTester
tester
)
async
{
const
Key
buttonKey
=
Key
(
'test'
);
const
Key
labelKey
=
Key
(
'label'
);
await
tester
.
pumpWidget
(
// When textDirection is set to TextDirection.ltr, the label appears on the
// right side of the icon. This is important in determining whether the
// horizontal padding is applied correctly later on
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Center
(
child:
FilledButton
.
icon
(
key:
buttonKey
,
style:
const
ButtonStyle
(
padding:
MaterialStatePropertyAll
<
EdgeInsets
>(
EdgeInsets
.
fromLTRB
(
16
,
5
,
10
,
12
)),
),
onPressed:
()
{},
icon:
const
Icon
(
Icons
.
add
),
label:
const
Text
(
'Hello'
,
key:
labelKey
,
),
),
),
),
);
final
Rect
paddingRect
=
tester
.
getRect
(
find
.
byType
(
Padding
));
final
Rect
labelRect
=
tester
.
getRect
(
find
.
byKey
(
labelKey
));
final
Rect
iconRect
=
tester
.
getRect
(
find
.
byType
(
Icon
));
// The right padding should be applied on the right of the label, whereas the
// left padding should be applied on the left side of the icon.
expect
(
paddingRect
.
right
,
labelRect
.
right
+
10
);
expect
(
paddingRect
.
left
,
iconRect
.
left
-
16
);
// Use the taller widget to check the top and bottom padding.
final
Rect
tallerWidget
=
iconRect
.
height
>
labelRect
.
height
?
iconRect
:
labelRect
;
expect
(
paddingRect
.
top
,
tallerWidget
.
top
-
5
);
expect
(
paddingRect
.
bottom
,
tallerWidget
.
bottom
+
12
);
});
group
(
'Default FilledButton padding for textScaleFactor, textDirection'
,
()
{
const
ValueKey
<
String
>
buttonKey
=
ValueKey
<
String
>(
'button'
);
const
ValueKey
<
String
>
labelKey
=
ValueKey
<
String
>(
'label'
);
const
ValueKey
<
String
>
iconKey
=
ValueKey
<
String
>(
'icon'
);
const
List
<
double
>
textScaleFactorOptions
=
<
double
>[
0.5
,
1.0
,
1.25
,
1.5
,
2.0
,
2.5
,
3.0
,
4.0
];
const
List
<
TextDirection
>
textDirectionOptions
=
<
TextDirection
>[
TextDirection
.
ltr
,
TextDirection
.
rtl
];
const
List
<
Widget
?>
iconOptions
=
<
Widget
?>[
null
,
Icon
(
Icons
.
add
,
size:
18
,
key:
iconKey
)];
// Expected values for each textScaleFactor.
final
Map
<
double
,
double
>
paddingWithoutIconStart
=
<
double
,
double
>{
0.5
:
16
,
1
:
16
,
1.25
:
14
,
1.5
:
12
,
2
:
8
,
2.5
:
6
,
3
:
4
,
4
:
4
,
};
final
Map
<
double
,
double
>
paddingWithoutIconEnd
=
<
double
,
double
>{
0.5
:
16
,
1
:
16
,
1.25
:
14
,
1.5
:
12
,
2
:
8
,
2.5
:
6
,
3
:
4
,
4
:
4
,
};
final
Map
<
double
,
double
>
paddingWithIconStart
=
<
double
,
double
>{
0.5
:
12
,
1
:
12
,
1.25
:
11
,
1.5
:
10
,
2
:
8
,
2.5
:
8
,
3
:
8
,
4
:
8
,
};
final
Map
<
double
,
double
>
paddingWithIconEnd
=
<
double
,
double
>{
0.5
:
16
,
1
:
16
,
1.25
:
14
,
1.5
:
12
,
2
:
8
,
2.5
:
6
,
3
:
4
,
4
:
4
,
};
final
Map
<
double
,
double
>
paddingWithIconGap
=
<
double
,
double
>{
0.5
:
8
,
1
:
8
,
1.25
:
7
,
1.5
:
6
,
2
:
4
,
2.5
:
4
,
3
:
4
,
4
:
4
,
};
Rect
globalBounds
(
RenderBox
renderBox
)
{
final
Offset
topLeft
=
renderBox
.
localToGlobal
(
Offset
.
zero
);
return
topLeft
&
renderBox
.
size
;
}
/// Computes the padding between two [Rect]s, one inside the other.
EdgeInsets
paddingBetween
({
required
Rect
parent
,
required
Rect
child
})
{
assert
(
parent
.
intersect
(
child
)
==
child
);
return
EdgeInsets
.
fromLTRB
(
child
.
left
-
parent
.
left
,
child
.
top
-
parent
.
top
,
parent
.
right
-
child
.
right
,
parent
.
bottom
-
child
.
bottom
,
);
}
for
(
final
double
textScaleFactor
in
textScaleFactorOptions
)
{
for
(
final
TextDirection
textDirection
in
textDirectionOptions
)
{
for
(
final
Widget
?
icon
in
iconOptions
)
{
final
String
testName
=
<
String
>[
'FilledButton, text scale
$textScaleFactor
'
,
if
(
icon
!=
null
)
'with icon'
,
if
(
textDirection
==
TextDirection
.
rtl
)
'RTL'
,
].
join
(
', '
);
testWidgets
(
testName
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
ThemeData
(
colorScheme:
const
ColorScheme
.
light
(),
// Force Material 2 defaults for the typography and size
// default values as the test was designed against these settings.
textTheme:
Typography
.
englishLike2014
,
filledButtonTheme:
FilledButtonThemeData
(
style:
FilledButton
.
styleFrom
(
minimumSize:
const
Size
(
64
,
36
)),
),
),
home:
Builder
(
builder:
(
BuildContext
context
)
{
return
MediaQuery
(
data:
MediaQuery
.
of
(
context
).
copyWith
(
textScaleFactor:
textScaleFactor
,
),
child:
Directionality
(
textDirection:
textDirection
,
child:
Scaffold
(
body:
Center
(
child:
icon
==
null
?
FilledButton
(
key:
buttonKey
,
onPressed:
()
{},
child:
const
Text
(
'button'
,
key:
labelKey
),
)
:
FilledButton
.
icon
(
key:
buttonKey
,
onPressed:
()
{},
icon:
icon
,
label:
const
Text
(
'button'
,
key:
labelKey
),
),
),
),
),
);
},
),
),
);
final
Element
paddingElement
=
tester
.
element
(
find
.
descendant
(
of:
find
.
byKey
(
buttonKey
),
matching:
find
.
byType
(
Padding
),
),
);
expect
(
Directionality
.
of
(
paddingElement
),
textDirection
);
final
Padding
paddingWidget
=
paddingElement
.
widget
as
Padding
;
// Compute expected padding, and check.
final
double
expectedStart
=
icon
!=
null
?
paddingWithIconStart
[
textScaleFactor
]!
:
paddingWithoutIconStart
[
textScaleFactor
]!;
final
double
expectedEnd
=
icon
!=
null
?
paddingWithIconEnd
[
textScaleFactor
]!
:
paddingWithoutIconEnd
[
textScaleFactor
]!;
final
EdgeInsets
expectedPadding
=
EdgeInsetsDirectional
.
fromSTEB
(
expectedStart
,
0
,
expectedEnd
,
0
)
.
resolve
(
textDirection
);
expect
(
paddingWidget
.
padding
.
resolve
(
textDirection
),
expectedPadding
);
// Measure padding in terms of the difference between the button and its label child
// and check that.
final
RenderBox
labelRenderBox
=
tester
.
renderObject
<
RenderBox
>(
find
.
byKey
(
labelKey
));
final
Rect
labelBounds
=
globalBounds
(
labelRenderBox
);
final
RenderBox
?
iconRenderBox
=
icon
==
null
?
null
:
tester
.
renderObject
<
RenderBox
>(
find
.
byKey
(
iconKey
));
final
Rect
?
iconBounds
=
icon
==
null
?
null
:
globalBounds
(
iconRenderBox
!);
final
Rect
childBounds
=
icon
==
null
?
labelBounds
:
labelBounds
.
expandToInclude
(
iconBounds
!);
// We measure the `InkResponse` descendant of the button
// element, because the button has a larger `RenderBox`
// which accommodates the minimum tap target with a height
// of 48.
final
RenderBox
buttonRenderBox
=
tester
.
renderObject
<
RenderBox
>(
find
.
descendant
(
of:
find
.
byKey
(
buttonKey
),
matching:
find
.
byWidgetPredicate
(
(
Widget
widget
)
=>
widget
is
InkResponse
,
),
),
);
final
Rect
buttonBounds
=
globalBounds
(
buttonRenderBox
);
final
EdgeInsets
visuallyMeasuredPadding
=
paddingBetween
(
parent:
buttonBounds
,
child:
childBounds
,
);
// Since there is a requirement of a minimum width of 64
// and a minimum height of 36 on material buttons, the visual
// padding of smaller buttons may not match their settings.
// Therefore, we only test buttons that are large enough.
if
(
buttonBounds
.
width
>
64
)
{
expect
(
visuallyMeasuredPadding
.
left
,
expectedPadding
.
left
,
);
expect
(
visuallyMeasuredPadding
.
right
,
expectedPadding
.
right
,
);
}
if
(
buttonBounds
.
height
>
36
)
{
expect
(
visuallyMeasuredPadding
.
top
,
expectedPadding
.
top
,
);
expect
(
visuallyMeasuredPadding
.
bottom
,
expectedPadding
.
bottom
,
);
}
// Check the gap between the icon and the label
if
(
icon
!=
null
)
{
final
double
gapWidth
=
textDirection
==
TextDirection
.
ltr
?
labelBounds
.
left
-
iconBounds
!.
right
:
iconBounds
!.
left
-
labelBounds
.
right
;
expect
(
gapWidth
,
paddingWithIconGap
[
textScaleFactor
]);
}
// Check the text's height - should be consistent with the textScaleFactor.
final
RenderBox
textRenderObject
=
tester
.
renderObject
<
RenderBox
>(
find
.
descendant
(
of:
find
.
byKey
(
labelKey
),
matching:
find
.
byElementPredicate
(
(
Element
element
)
=>
element
.
widget
is
RichText
,
),
),
);
final
double
textHeight
=
textRenderObject
.
paintBounds
.
size
.
height
;
final
double
expectedTextHeight
=
14
*
textScaleFactor
;
expect
(
textHeight
,
moreOrLessEquals
(
expectedTextHeight
,
epsilon:
0.5
));
});
}
}
}
});
testWidgets
(
'Override FilledButton default padding'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
ThemeData
.
from
(
colorScheme:
const
ColorScheme
.
light
()),
home:
Builder
(
builder:
(
BuildContext
context
)
{
return
MediaQuery
(
data:
MediaQuery
.
of
(
context
).
copyWith
(
textScaleFactor:
2
,
),
child:
Scaffold
(
body:
Center
(
child:
FilledButton
(
style:
FilledButton
.
styleFrom
(
padding:
const
EdgeInsets
.
all
(
22
)),
onPressed:
()
{},
child:
const
Text
(
'FilledButton'
),
),
),
),
);
},
),
),
);
final
Padding
paddingWidget
=
tester
.
widget
<
Padding
>(
find
.
descendant
(
of:
find
.
byType
(
FilledButton
),
matching:
find
.
byType
(
Padding
),
),
);
expect
(
paddingWidget
.
padding
,
const
EdgeInsets
.
all
(
22
));
});
testWidgets
(
'By default, FilledButton shape outline is defined by shape.side'
,
(
WidgetTester
tester
)
async
{
const
Color
borderColor
=
Color
(
0xff4caf50
);
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
ThemeData
(
colorScheme:
const
ColorScheme
.
light
(),
textTheme:
Typography
.
englishLike2014
),
home:
Center
(
child:
FilledButton
(
style:
FilledButton
.
styleFrom
(
shape:
const
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
all
(
Radius
.
circular
(
16
)),
side:
BorderSide
(
width:
10
,
color:
borderColor
),
),
minimumSize:
const
Size
(
64
,
36
),
),
onPressed:
()
{},
child:
const
Text
(
'button'
),
),
),
),
);
expect
(
find
.
byType
(
FilledButton
),
paints
..
drrect
(
// Outer and inner rect that give the outline a width of 10.
outer:
RRect
.
fromLTRBR
(
0.0
,
0.0
,
116.0
,
36.0
,
const
Radius
.
circular
(
16
)),
inner:
RRect
.
fromLTRBR
(
10.0
,
10.0
,
106.0
,
26.0
,
const
Radius
.
circular
(
16
-
10
)),
color:
borderColor
)
);
});
testWidgets
(
'Fixed size FilledButtons'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Scaffold
(
body:
Column
(
mainAxisSize:
MainAxisSize
.
min
,
children:
<
Widget
>[
FilledButton
(
style:
FilledButton
.
styleFrom
(
fixedSize:
const
Size
(
100
,
100
)),
onPressed:
()
{},
child:
const
Text
(
'100x100'
),
),
FilledButton
(
style:
FilledButton
.
styleFrom
(
fixedSize:
const
Size
.
fromWidth
(
200
)),
onPressed:
()
{},
child:
const
Text
(
'200xh'
),
),
FilledButton
(
style:
FilledButton
.
styleFrom
(
fixedSize:
const
Size
.
fromHeight
(
200
)),
onPressed:
()
{},
child:
const
Text
(
'wx200'
),
),
],
),
),
),
);
expect
(
tester
.
getSize
(
find
.
widgetWithText
(
FilledButton
,
'100x100'
)),
const
Size
(
100
,
100
));
expect
(
tester
.
getSize
(
find
.
widgetWithText
(
FilledButton
,
'200xh'
)).
width
,
200
);
expect
(
tester
.
getSize
(
find
.
widgetWithText
(
FilledButton
,
'wx200'
)).
height
,
200
);
});
testWidgets
(
'FilledButton with NoSplash splashFactory paints nothing'
,
(
WidgetTester
tester
)
async
{
Widget
buildFrame
({
InteractiveInkFeatureFactory
?
splashFactory
})
{
return
MaterialApp
(
home:
Scaffold
(
body:
Center
(
child:
FilledButton
(
style:
FilledButton
.
styleFrom
(
splashFactory:
splashFactory
,
),
onPressed:
()
{
},
child:
const
Text
(
'test'
),
),
),
),
);
}
// NoSplash.splashFactory, no splash circles drawn
await
tester
.
pumpWidget
(
buildFrame
(
splashFactory:
NoSplash
.
splashFactory
));
{
final
TestGesture
gesture
=
await
tester
.
startGesture
(
tester
.
getCenter
(
find
.
text
(
'test'
)));
final
MaterialInkController
material
=
Material
.
of
(
tester
.
element
(
find
.
text
(
'test'
)))!;
await
tester
.
pump
(
const
Duration
(
milliseconds:
200
));
expect
(
material
,
paintsExactlyCountTimes
(
#drawCircle
,
0
));
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
}
// InkRipple.splashFactory, one splash circle drawn.
await
tester
.
pumpWidget
(
buildFrame
(
splashFactory:
InkRipple
.
splashFactory
));
{
final
TestGesture
gesture
=
await
tester
.
startGesture
(
tester
.
getCenter
(
find
.
text
(
'test'
)));
final
MaterialInkController
material
=
Material
.
of
(
tester
.
element
(
find
.
text
(
'test'
)))!;
await
tester
.
pump
(
const
Duration
(
milliseconds:
200
));
expect
(
material
,
paintsExactlyCountTimes
(
#drawCircle
,
1
));
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
}
});
testWidgets
(
'FilledButton uses InkSparkle only for Android non-web when useMaterial3 is true'
,
(
WidgetTester
tester
)
async
{
final
ThemeData
theme
=
ThemeData
(
useMaterial3:
true
);
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
theme
,
home:
Center
(
child:
FilledButton
(
onPressed:
()
{
},
child:
const
Text
(
'button'
),
),
),
),
);
final
InkWell
buttonInkWell
=
tester
.
widget
<
InkWell
>(
find
.
descendant
(
of:
find
.
byType
(
FilledButton
),
matching:
find
.
byType
(
InkWell
),
));
if
(
debugDefaultTargetPlatformOverride
!
==
TargetPlatform
.
android
&&
!
kIsWeb
)
{
expect
(
buttonInkWell
.
splashFactory
,
equals
(
InkSparkle
.
splashFactory
));
}
else
{
expect
(
buttonInkWell
.
splashFactory
,
equals
(
InkRipple
.
splashFactory
));
}
},
variant:
TargetPlatformVariant
.
all
());
testWidgets
(
'FilledButton.icon does not overflow'
,
(
WidgetTester
tester
)
async
{
// Regression test for https://github.com/flutter/flutter/issues/77815
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Scaffold
(
body:
SizedBox
(
width:
200
,
child:
FilledButton
.
icon
(
onPressed:
()
{},
icon:
const
Icon
(
Icons
.
add
),
label:
const
Text
(
// Much wider than 200
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut a euismod nibh. Morbi laoreet purus.'
,
),
),
),
),
),
);
expect
(
tester
.
takeException
(),
null
);
});
testWidgets
(
'FilledButton.icon icon,label layout'
,
(
WidgetTester
tester
)
async
{
final
Key
buttonKey
=
UniqueKey
();
final
Key
iconKey
=
UniqueKey
();
final
Key
labelKey
=
UniqueKey
();
final
ButtonStyle
style
=
FilledButton
.
styleFrom
(
padding:
EdgeInsets
.
zero
,
visualDensity:
VisualDensity
.
standard
,
// dx=0, dy=0
);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Scaffold
(
body:
SizedBox
(
width:
200
,
child:
FilledButton
.
icon
(
key:
buttonKey
,
style:
style
,
onPressed:
()
{},
icon:
SizedBox
(
key:
iconKey
,
width:
50
,
height:
100
),
label:
SizedBox
(
key:
labelKey
,
width:
50
,
height:
100
),
),
),
),
),
);
// The button's label and icon are separated by a gap of 8:
// 46 [icon 50] 8 [label 50] 46
// The overall button width is 200. So:
// icon.x = 46
// label.x = 46 + 50 + 8 = 104
expect
(
tester
.
getRect
(
find
.
byKey
(
buttonKey
)),
const
Rect
.
fromLTRB
(
0.0
,
0.0
,
200.0
,
100.0
));
expect
(
tester
.
getRect
(
find
.
byKey
(
iconKey
)),
const
Rect
.
fromLTRB
(
46.0
,
0.0
,
96.0
,
100.0
));
expect
(
tester
.
getRect
(
find
.
byKey
(
labelKey
)),
const
Rect
.
fromLTRB
(
104.0
,
0.0
,
154.0
,
100.0
));
});
testWidgets
(
'FilledButton maximumSize'
,
(
WidgetTester
tester
)
async
{
final
Key
key0
=
UniqueKey
();
final
Key
key1
=
UniqueKey
();
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
ThemeData
(
textTheme:
Typography
.
englishLike2014
),
home:
Scaffold
(
body:
Center
(
child:
Column
(
mainAxisSize:
MainAxisSize
.
min
,
children:
<
Widget
>[
FilledButton
(
key:
key0
,
style:
TextButton
.
styleFrom
(
minimumSize:
const
Size
(
24
,
36
),
maximumSize:
const
Size
.
fromWidth
(
64
),
),
onPressed:
()
{
},
child:
const
Text
(
'A B C D E F G H I J K L M N O P'
),
),
FilledButton
.
icon
(
key:
key1
,
style:
TextButton
.
styleFrom
(
minimumSize:
const
Size
(
24
,
36
),
maximumSize:
const
Size
.
fromWidth
(
104
),
),
onPressed:
()
{},
icon:
Container
(
color:
Colors
.
red
,
width:
32
,
height:
32
),
label:
const
Text
(
'A B C D E F G H I J K L M N O P'
),
),
],
),
),
),
),
);
expect
(
tester
.
getSize
(
find
.
byKey
(
key0
)),
const
Size
(
64.0
,
224.0
));
expect
(
tester
.
getSize
(
find
.
byKey
(
key1
)),
const
Size
(
104.0
,
224.0
));
});
testWidgets
(
'Fixed size FilledButton, same as minimumSize == maximumSize'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Scaffold
(
body:
Column
(
mainAxisSize:
MainAxisSize
.
min
,
children:
<
Widget
>[
FilledButton
(
style:
FilledButton
.
styleFrom
(
fixedSize:
const
Size
(
200
,
200
)),
onPressed:
()
{
},
child:
const
Text
(
'200x200'
),
),
FilledButton
(
style:
FilledButton
.
styleFrom
(
minimumSize:
const
Size
(
200
,
200
),
maximumSize:
const
Size
(
200
,
200
),
),
onPressed:
()
{
},
child:
const
Text
(
'200,200'
),
),
],
),
),
),
);
expect
(
tester
.
getSize
(
find
.
widgetWithText
(
FilledButton
,
'200x200'
)),
const
Size
(
200
,
200
));
expect
(
tester
.
getSize
(
find
.
widgetWithText
(
FilledButton
,
'200,200'
)),
const
Size
(
200
,
200
));
});
testWidgets
(
'FilledButton changes mouse cursor when hovered'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
MouseRegion
(
cursor:
SystemMouseCursors
.
forbidden
,
child:
FilledButton
(
style:
FilledButton
.
styleFrom
(
enabledMouseCursor:
SystemMouseCursors
.
text
,
disabledMouseCursor:
SystemMouseCursors
.
grab
,
),
onPressed:
()
{},
child:
const
Text
(
'button'
),
),
),
),
);
final
TestGesture
gesture
=
await
tester
.
createGesture
(
kind:
PointerDeviceKind
.
mouse
,
pointer:
1
);
await
gesture
.
addPointer
(
location:
Offset
.
zero
);
await
tester
.
pump
();
expect
(
RendererBinding
.
instance
.
mouseTracker
.
debugDeviceActiveCursor
(
1
),
SystemMouseCursors
.
text
);
// Test cursor when disabled
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
MouseRegion
(
cursor:
SystemMouseCursors
.
forbidden
,
child:
FilledButton
(
style:
FilledButton
.
styleFrom
(
enabledMouseCursor:
SystemMouseCursors
.
text
,
disabledMouseCursor:
SystemMouseCursors
.
grab
,
),
onPressed:
null
,
child:
const
Text
(
'button'
),
),
),
),
);
expect
(
RendererBinding
.
instance
.
mouseTracker
.
debugDeviceActiveCursor
(
1
),
SystemMouseCursors
.
grab
);
// Test default cursor
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
MouseRegion
(
cursor:
SystemMouseCursors
.
forbidden
,
child:
FilledButton
(
onPressed:
()
{},
child:
const
Text
(
'button'
),
),
),
),
);
expect
(
RendererBinding
.
instance
.
mouseTracker
.
debugDeviceActiveCursor
(
1
),
SystemMouseCursors
.
click
);
// Test default cursor when disabled
await
tester
.
pumpWidget
(
const
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
MouseRegion
(
cursor:
SystemMouseCursors
.
forbidden
,
child:
FilledButton
(
onPressed:
null
,
child:
Text
(
'button'
),
),
),
),
);
expect
(
RendererBinding
.
instance
.
mouseTracker
.
debugDeviceActiveCursor
(
1
),
SystemMouseCursors
.
basic
);
});
testWidgets
(
'Ink Response shape matches Material shape'
,
(
WidgetTester
tester
)
async
{
Widget
buildFrame
({
BorderSide
?
side
})
{
return
MaterialApp
(
home:
Scaffold
(
body:
Center
(
child:
FilledButton
(
style:
FilledButton
.
styleFrom
(
side:
side
,
shape:
const
RoundedRectangleBorder
(
side:
BorderSide
(
color:
Color
(
0xff0000ff
),
width:
0
,
),
),
),
onPressed:
()
{
},
child:
const
Text
(
'FilledButton'
),
),
),
),
);
}
const
BorderSide
borderSide
=
BorderSide
(
width:
10
,
color:
Color
(
0xff00ff00
));
await
tester
.
pumpWidget
(
buildFrame
(
side:
borderSide
));
expect
(
tester
.
widget
<
InkWell
>(
find
.
byType
(
InkWell
)).
customBorder
,
const
RoundedRectangleBorder
(
side:
borderSide
),
);
await
tester
.
pumpWidget
(
buildFrame
());
await
tester
.
pumpAndSettle
();
expect
(
tester
.
widget
<
InkWell
>(
find
.
byType
(
InkWell
)).
customBorder
,
const
RoundedRectangleBorder
(
side:
BorderSide
(
color:
Color
(
0xff0000ff
),
width:
0.0
,
),
),
);
});
testWidgets
(
'FilledButton.styleFrom can be used to set foreground and background colors'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Scaffold
(
body:
FilledButton
(
style:
FilledButton
.
styleFrom
(
foregroundColor:
Colors
.
white
,
backgroundColor:
Colors
.
purple
,
),
onPressed:
()
{},
child:
const
Text
(
'button'
),
),
),
),
);
final
Material
material
=
tester
.
widget
<
Material
>(
find
.
descendant
(
of:
find
.
byType
(
FilledButton
),
matching:
find
.
byType
(
Material
),
));
expect
(
material
.
color
,
Colors
.
purple
);
expect
(
material
.
textStyle
!.
color
,
Colors
.
white
);
});
testWidgets
(
'FilledButton statesController'
,
(
WidgetTester
tester
)
async
{
int
count
=
0
;
void
valueChanged
()
{
count
+=
1
;
}
final
MaterialStatesController
controller
=
MaterialStatesController
();
controller
.
addListener
(
valueChanged
);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Center
(
child:
FilledButton
(
statesController:
controller
,
onPressed:
()
{
},
child:
const
Text
(
'button'
),
),
),
),
);
expect
(
controller
.
value
,
<
MaterialState
>{});
expect
(
count
,
0
);
final
Offset
center
=
tester
.
getCenter
(
find
.
byType
(
FilledButton
));
final
TestGesture
gesture
=
await
tester
.
createGesture
(
kind:
PointerDeviceKind
.
mouse
,
);
await
gesture
.
addPointer
();
await
gesture
.
moveTo
(
center
);
await
tester
.
pumpAndSettle
();
expect
(
controller
.
value
,
<
MaterialState
>{
MaterialState
.
hovered
});
expect
(
count
,
1
);
await
gesture
.
moveTo
(
Offset
.
zero
);
await
tester
.
pumpAndSettle
();
expect
(
controller
.
value
,
<
MaterialState
>{});
expect
(
count
,
2
);
await
gesture
.
moveTo
(
center
);
await
tester
.
pumpAndSettle
();
expect
(
controller
.
value
,
<
MaterialState
>{
MaterialState
.
hovered
});
expect
(
count
,
3
);
await
gesture
.
down
(
center
);
await
tester
.
pumpAndSettle
();
expect
(
controller
.
value
,
<
MaterialState
>{
MaterialState
.
hovered
,
MaterialState
.
pressed
});
expect
(
count
,
4
);
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
controller
.
value
,
<
MaterialState
>{
MaterialState
.
hovered
});
expect
(
count
,
5
);
await
gesture
.
moveTo
(
Offset
.
zero
);
await
tester
.
pumpAndSettle
();
expect
(
controller
.
value
,
<
MaterialState
>{});
expect
(
count
,
6
);
await
gesture
.
down
(
center
);
await
tester
.
pumpAndSettle
();
expect
(
controller
.
value
,
<
MaterialState
>{
MaterialState
.
hovered
,
MaterialState
.
pressed
});
expect
(
count
,
8
);
// adds hovered and pressed - two changes
// If the button is rebuilt disabled, then the pressed state is
// removed.
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Center
(
child:
FilledButton
(
statesController:
controller
,
onPressed:
null
,
child:
const
Text
(
'button'
),
),
),
),
);
await
tester
.
pumpAndSettle
();
expect
(
controller
.
value
,
<
MaterialState
>{
MaterialState
.
hovered
,
MaterialState
.
disabled
});
expect
(
count
,
10
);
// removes pressed and adds disabled - two changes
await
gesture
.
moveTo
(
Offset
.
zero
);
await
tester
.
pumpAndSettle
();
expect
(
controller
.
value
,
<
MaterialState
>{
MaterialState
.
disabled
});
expect
(
count
,
11
);
await
gesture
.
removePointer
();
});
testWidgets
(
'Disabled FilledButton statesController'
,
(
WidgetTester
tester
)
async
{
int
count
=
0
;
void
valueChanged
()
{
count
+=
1
;
}
final
MaterialStatesController
controller
=
MaterialStatesController
();
controller
.
addListener
(
valueChanged
);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Center
(
child:
FilledButton
(
statesController:
controller
,
onPressed:
null
,
child:
const
Text
(
'button'
),
),
),
),
);
expect
(
controller
.
value
,
<
MaterialState
>{
MaterialState
.
disabled
});
expect
(
count
,
1
);
});
}
TextStyle
_iconStyle
(
WidgetTester
tester
,
IconData
icon
)
{
final
RichText
iconRichText
=
tester
.
widget
<
RichText
>(
find
.
descendant
(
of:
find
.
byIcon
(
icon
),
matching:
find
.
byType
(
RichText
)),
);
return
iconRichText
.
text
.
style
!;
}
packages/flutter/test/material/filled_button_theme_test.dart
0 → 100644
View file @
5454bab6
// 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
'package:flutter/material.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
void
main
(
)
{
testWidgets
(
'Passing no FilledButtonTheme returns defaults'
,
(
WidgetTester
tester
)
async
{
const
ColorScheme
colorScheme
=
ColorScheme
.
light
();
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
ThemeData
.
from
(
colorScheme:
colorScheme
),
home:
Scaffold
(
body:
Center
(
child:
FilledButton
(
onPressed:
()
{
},
child:
const
Text
(
'button'
),
),
),
),
),
);
final
Finder
buttonMaterial
=
find
.
descendant
(
of:
find
.
byType
(
FilledButton
),
matching:
find
.
byType
(
Material
),
);
final
Material
material
=
tester
.
widget
<
Material
>(
buttonMaterial
);
expect
(
material
.
animationDuration
,
const
Duration
(
milliseconds:
200
));
expect
(
material
.
borderRadius
,
null
);
expect
(
material
.
color
,
colorScheme
.
primary
);
expect
(
material
.
elevation
,
0
);
expect
(
material
.
shadowColor
,
const
Color
(
0xff000000
));
expect
(
material
.
shape
,
const
StadiumBorder
());
expect
(
material
.
textStyle
!.
color
,
colorScheme
.
onPrimary
);
expect
(
material
.
textStyle
!.
fontFamily
,
'Roboto'
);
expect
(
material
.
textStyle
!.
fontSize
,
14
);
expect
(
material
.
textStyle
!.
fontWeight
,
FontWeight
.
w500
);
final
Align
align
=
tester
.
firstWidget
<
Align
>(
find
.
ancestor
(
of:
find
.
text
(
'button'
),
matching:
find
.
byType
(
Align
)));
expect
(
align
.
alignment
,
Alignment
.
center
);
});
group
(
'[Theme, TextTheme, FilledButton style overrides]'
,
()
{
const
Color
foregroundColor
=
Color
(
0xff000001
);
const
Color
backgroundColor
=
Color
(
0xff000002
);
const
Color
disabledForegroundColor
=
Color
(
0xff000003
);
const
Color
disabledBackgroundColor
=
Color
(
0xff000004
);
const
Color
shadowColor
=
Color
(
0xff000005
);
const
double
elevation
=
1
;
const
TextStyle
textStyle
=
TextStyle
(
fontSize:
12.0
);
const
EdgeInsets
padding
=
EdgeInsets
.
all
(
3
);
const
Size
minimumSize
=
Size
(
200
,
200
);
const
BorderSide
side
=
BorderSide
(
color:
Colors
.
green
,
width:
2
);
const
OutlinedBorder
shape
=
RoundedRectangleBorder
(
side:
side
,
borderRadius:
BorderRadius
.
all
(
Radius
.
circular
(
2
)));
const
MouseCursor
enabledMouseCursor
=
SystemMouseCursors
.
text
;
const
MouseCursor
disabledMouseCursor
=
SystemMouseCursors
.
grab
;
const
MaterialTapTargetSize
tapTargetSize
=
MaterialTapTargetSize
.
shrinkWrap
;
const
Duration
animationDuration
=
Duration
(
milliseconds:
25
);
const
bool
enableFeedback
=
false
;
const
AlignmentGeometry
alignment
=
Alignment
.
centerLeft
;
final
ButtonStyle
style
=
FilledButton
.
styleFrom
(
foregroundColor:
foregroundColor
,
backgroundColor:
backgroundColor
,
disabledForegroundColor:
disabledForegroundColor
,
disabledBackgroundColor:
disabledBackgroundColor
,
shadowColor:
shadowColor
,
elevation:
elevation
,
textStyle:
textStyle
,
padding:
padding
,
minimumSize:
minimumSize
,
side:
side
,
shape:
shape
,
enabledMouseCursor:
enabledMouseCursor
,
disabledMouseCursor:
disabledMouseCursor
,
tapTargetSize:
tapTargetSize
,
animationDuration:
animationDuration
,
enableFeedback:
enableFeedback
,
alignment:
alignment
,
);
Widget
buildFrame
({
ButtonStyle
?
buttonStyle
,
ButtonStyle
?
themeStyle
,
ButtonStyle
?
overallStyle
})
{
final
Widget
child
=
Builder
(
builder:
(
BuildContext
context
)
{
return
FilledButton
(
style:
buttonStyle
,
onPressed:
()
{
},
child:
const
Text
(
'button'
),
);
},
);
return
MaterialApp
(
theme:
ThemeData
.
from
(
colorScheme:
const
ColorScheme
.
light
()).
copyWith
(
filledButtonTheme:
FilledButtonThemeData
(
style:
overallStyle
),
),
home:
Scaffold
(
body:
Center
(
// If the FilledButtonTheme widget is present, it's used
// instead of the Theme's ThemeData.FilledButtonTheme.
child:
themeStyle
==
null
?
child
:
FilledButtonTheme
(
data:
FilledButtonThemeData
(
style:
themeStyle
),
child:
child
,
),
),
),
);
}
final
Finder
findMaterial
=
find
.
descendant
(
of:
find
.
byType
(
FilledButton
),
matching:
find
.
byType
(
Material
),
);
final
Finder
findInkWell
=
find
.
descendant
(
of:
find
.
byType
(
FilledButton
),
matching:
find
.
byType
(
InkWell
),
);
const
Set
<
MaterialState
>
enabled
=
<
MaterialState
>{};
const
Set
<
MaterialState
>
disabled
=
<
MaterialState
>{
MaterialState
.
disabled
};
const
Set
<
MaterialState
>
hovered
=
<
MaterialState
>{
MaterialState
.
hovered
};
const
Set
<
MaterialState
>
focused
=
<
MaterialState
>{
MaterialState
.
focused
};
const
Set
<
MaterialState
>
pressed
=
<
MaterialState
>{
MaterialState
.
pressed
};
void
checkButton
(
WidgetTester
tester
)
{
final
Material
material
=
tester
.
widget
<
Material
>(
findMaterial
);
final
InkWell
inkWell
=
tester
.
widget
<
InkWell
>(
findInkWell
);
expect
(
material
.
textStyle
!.
color
,
foregroundColor
);
expect
(
material
.
textStyle
!.
fontSize
,
12
);
expect
(
material
.
color
,
backgroundColor
);
expect
(
material
.
shadowColor
,
shadowColor
);
expect
(
material
.
elevation
,
elevation
);
expect
(
MaterialStateProperty
.
resolveAs
<
MouseCursor
>(
inkWell
.
mouseCursor
!,
enabled
),
enabledMouseCursor
);
expect
(
MaterialStateProperty
.
resolveAs
<
MouseCursor
>(
inkWell
.
mouseCursor
!,
disabled
),
disabledMouseCursor
);
expect
(
inkWell
.
overlayColor
!.
resolve
(
hovered
),
foregroundColor
.
withOpacity
(
0.08
));
expect
(
inkWell
.
overlayColor
!.
resolve
(
focused
),
foregroundColor
.
withOpacity
(
0.12
));
expect
(
inkWell
.
overlayColor
!.
resolve
(
pressed
),
foregroundColor
.
withOpacity
(
0.12
));
expect
(
inkWell
.
enableFeedback
,
enableFeedback
);
expect
(
material
.
borderRadius
,
null
);
expect
(
material
.
shape
,
shape
);
expect
(
material
.
animationDuration
,
animationDuration
);
expect
(
tester
.
getSize
(
find
.
byType
(
FilledButton
)),
const
Size
(
200
,
200
));
final
Align
align
=
tester
.
firstWidget
<
Align
>(
find
.
ancestor
(
of:
find
.
text
(
'button'
),
matching:
find
.
byType
(
Align
)));
expect
(
align
.
alignment
,
alignment
);
}
testWidgets
(
'Button style overrides defaults'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
buildFrame
(
buttonStyle:
style
));
await
tester
.
pumpAndSettle
();
// allow the animations to finish
checkButton
(
tester
);
});
testWidgets
(
'Button theme style overrides defaults'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
buildFrame
(
themeStyle:
style
));
await
tester
.
pumpAndSettle
();
checkButton
(
tester
);
});
testWidgets
(
'Overall Theme button theme style overrides defaults'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
buildFrame
(
overallStyle:
style
));
await
tester
.
pumpAndSettle
();
checkButton
(
tester
);
});
// Same as the previous tests with empty ButtonStyle's instead of null.
testWidgets
(
'Button style overrides defaults, empty theme and overall styles'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
buildFrame
(
buttonStyle:
style
,
themeStyle:
const
ButtonStyle
(),
overallStyle:
const
ButtonStyle
()));
await
tester
.
pumpAndSettle
();
// allow the animations to finish
checkButton
(
tester
);
});
testWidgets
(
'Button theme style overrides defaults, empty button and overall styles'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
buildFrame
(
buttonStyle:
const
ButtonStyle
(),
themeStyle:
style
,
overallStyle:
const
ButtonStyle
()));
await
tester
.
pumpAndSettle
();
// allow the animations to finish
checkButton
(
tester
);
});
testWidgets
(
'Overall Theme button theme style overrides defaults, null theme and empty overall style'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
buildFrame
(
buttonStyle:
const
ButtonStyle
(),
overallStyle:
style
));
await
tester
.
pumpAndSettle
();
// allow the animations to finish
checkButton
(
tester
);
});
});
testWidgets
(
'Theme shadowColor'
,
(
WidgetTester
tester
)
async
{
const
ColorScheme
colorScheme
=
ColorScheme
.
light
();
const
Color
shadowColor
=
Color
(
0xff000001
);
const
Color
overriddenColor
=
Color
(
0xff000002
);
Widget
buildFrame
({
Color
?
overallShadowColor
,
Color
?
themeShadowColor
,
Color
?
shadowColor
})
{
return
MaterialApp
(
theme:
ThemeData
.
from
(
colorScheme:
colorScheme
).
copyWith
(
shadowColor:
overallShadowColor
,
),
home:
Scaffold
(
body:
Center
(
child:
FilledButtonTheme
(
data:
FilledButtonThemeData
(
style:
FilledButton
.
styleFrom
(
shadowColor:
themeShadowColor
,
),
),
child:
Builder
(
builder:
(
BuildContext
context
)
{
return
FilledButton
(
style:
FilledButton
.
styleFrom
(
shadowColor:
shadowColor
,
),
onPressed:
()
{
},
child:
const
Text
(
'button'
),
);
},
),
),
),
),
);
}
final
Finder
buttonMaterialFinder
=
find
.
descendant
(
of:
find
.
byType
(
FilledButton
),
matching:
find
.
byType
(
Material
),
);
await
tester
.
pumpWidget
(
buildFrame
());
Material
material
=
tester
.
widget
<
Material
>(
buttonMaterialFinder
);
expect
(
material
.
shadowColor
,
Colors
.
black
);
//default
await
tester
.
pumpWidget
(
buildFrame
(
themeShadowColor:
shadowColor
));
await
tester
.
pumpAndSettle
();
// theme animation
material
=
tester
.
widget
<
Material
>(
buttonMaterialFinder
);
expect
(
material
.
shadowColor
,
shadowColor
);
await
tester
.
pumpWidget
(
buildFrame
(
shadowColor:
shadowColor
));
await
tester
.
pumpAndSettle
();
// theme animation
material
=
tester
.
widget
<
Material
>(
buttonMaterialFinder
);
expect
(
material
.
shadowColor
,
shadowColor
);
await
tester
.
pumpWidget
(
buildFrame
(
overallShadowColor:
overriddenColor
,
themeShadowColor:
shadowColor
));
await
tester
.
pumpAndSettle
();
// theme animation
material
=
tester
.
widget
<
Material
>(
buttonMaterialFinder
);
expect
(
material
.
shadowColor
,
shadowColor
);
await
tester
.
pumpWidget
(
buildFrame
(
themeShadowColor:
overriddenColor
,
shadowColor:
shadowColor
));
await
tester
.
pumpAndSettle
();
// theme animation
material
=
tester
.
widget
<
Material
>(
buttonMaterialFinder
);
expect
(
material
.
shadowColor
,
shadowColor
);
});
}
packages/flutter/test/material/theme_data_test.dart
View file @
5454bab6
...
...
@@ -695,6 +695,7 @@ void main() {
drawerTheme:
const
DrawerThemeData
(),
elevatedButtonTheme:
ElevatedButtonThemeData
(
style:
ElevatedButton
.
styleFrom
(
backgroundColor:
Colors
.
green
)),
expansionTileTheme:
const
ExpansionTileThemeData
(
backgroundColor:
Colors
.
black
),
filledButtonTheme:
FilledButtonThemeData
(
style:
FilledButton
.
styleFrom
(
foregroundColor:
Colors
.
green
)),
floatingActionButtonTheme:
const
FloatingActionButtonThemeData
(
backgroundColor:
Colors
.
black
),
iconButtonTheme:
IconButtonThemeData
(
style:
IconButton
.
styleFrom
(
foregroundColor:
Colors
.
pink
)),
listTileTheme:
const
ListTileThemeData
(),
...
...
@@ -808,6 +809,7 @@ void main() {
drawerTheme:
const
DrawerThemeData
(),
elevatedButtonTheme:
const
ElevatedButtonThemeData
(),
expansionTileTheme:
const
ExpansionTileThemeData
(
backgroundColor:
Colors
.
black
),
filledButtonTheme:
const
FilledButtonThemeData
(),
floatingActionButtonTheme:
const
FloatingActionButtonThemeData
(
backgroundColor:
Colors
.
white
),
iconButtonTheme:
const
IconButtonThemeData
(),
listTileTheme:
const
ListTileThemeData
(),
...
...
@@ -907,6 +909,7 @@ void main() {
drawerTheme:
otherTheme
.
drawerTheme
,
elevatedButtonTheme:
otherTheme
.
elevatedButtonTheme
,
expansionTileTheme:
otherTheme
.
expansionTileTheme
,
filledButtonTheme:
otherTheme
.
filledButtonTheme
,
floatingActionButtonTheme:
otherTheme
.
floatingActionButtonTheme
,
iconButtonTheme:
otherTheme
.
iconButtonTheme
,
listTileTheme:
otherTheme
.
listTileTheme
,
...
...
@@ -1005,6 +1008,7 @@ void main() {
expect
(
themeDataCopy
.
drawerTheme
,
equals
(
otherTheme
.
drawerTheme
));
expect
(
themeDataCopy
.
elevatedButtonTheme
,
equals
(
otherTheme
.
elevatedButtonTheme
));
expect
(
themeDataCopy
.
expansionTileTheme
,
equals
(
otherTheme
.
expansionTileTheme
));
expect
(
themeDataCopy
.
filledButtonTheme
,
equals
(
otherTheme
.
filledButtonTheme
));
expect
(
themeDataCopy
.
floatingActionButtonTheme
,
equals
(
otherTheme
.
floatingActionButtonTheme
));
expect
(
themeDataCopy
.
iconButtonTheme
,
equals
(
otherTheme
.
iconButtonTheme
));
expect
(
themeDataCopy
.
listTileTheme
,
equals
(
otherTheme
.
listTileTheme
));
...
...
@@ -1140,6 +1144,7 @@ void main() {
'dividerTheme'
,
'drawerTheme'
,
'elevatedButtonTheme'
,
'filledButtonTheme'
,
'floatingActionButtonTheme'
,
'iconButtonTheme'
,
'listTileTheme'
,
...
...
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