关于ButtonStyleButton样式的设置问题,包括ElevatedButton
的全局样式,单个按钮的样式设置,还有MaterialStateProperty
的使用。
修改单个按钮样式
当你要修改单个ElevatedButton
的样式时,可以调用静态方法styleFrom
设置它的style
属性,如下
dart
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue
),
onPressed: () => print('clicked'),
child: const Text('custom style for a ElevatedButton')
)
上面通过styleFrom
方法只是设置了背景色,保留了其他默认的样式,ElevatedButton
的默认样式有两种风格:一种是使用了M3的,调用了_ElevatedButtonDefaultsM3
方法来设置的(具体看源码);一种是没有使用M3的,具体如下:
dart
{
backgroundColor: colorScheme.primary, // 文本颜色
foregroundColor: colorScheme.onPrimary, // 背景颜色
disabledBackgroundColor: colorScheme.onSurface.withOpacity(0.12),
disabledForegroundColor: colorScheme.onSurface.withOpacity(0.38),
shadowColor: theme.shadowColor, // 鼠标hover时出现的阴影颜色
elevation: 2,
textStyle: theme.textTheme.labelLarge, // 文本样式
padding: _scaledPadding(context), // 内边距
minimumSize: const Size(64, 36),
maximumSize: Size.infinite,
// 外型
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4))),
enabledMouseCursor: SystemMouseCursors.click,
disabledMouseCursor: SystemMouseCursors.basic,
visualDensity: theme.visualDensity,
tapTargetSize: theme.materialTapTargetSize,
animationDuration: kThemeChangeDuration,
enableFeedback: true,
// 子组件的对齐方式
alignment: Alignment.center,
splashFactory: InkRipple.splashFactory
}
修改所有ElevatedButton样式
需要设置MaterialApp的theme属性,如下
dart
MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blueGrey,
foregroundColor: Colors.orangeAccent,
shadowColor: Colors.orange,
surfaceTintColor: Colors.red,
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4)))
),
)
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
)
使用ButtonStyle
上面不管是ElevatedButton.style
还是ElevatedButtonThemeData.style
都是ButtonStyle
类型,所以我们还可以直接实例化一个ButtonStyle
来设置ElevatedButton
的样式。
dart
ElevatedButton(
onPressed: () {},
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.black),
textStyle: MaterialStateProperty.all(
const TextStyle(
fontSize: 24
)
),
),
child: const Text('My Button')
)
上面的例子中直接设置了style属性为ButtonStyle实例,不过跟直接使用styleFrom方法不同的是,这里的属性都是MaterialStateProperty类型(styleFrom中是直接的数值类型,如Color,double等)。
结合上面的例子,这里还有一个隐藏的问题。我们前面通过ElevatedButtonThemeData全局设置了ElevatedButton的样式,然后又通过ButtonStyle设置了单个ElevatedButton的样式,如果这两个分别都设置了textStyle样式,那你最好两个地方设置相同的TextStyle属性,方便flutter在热启动时,做样式的过度动画,否则热启动时会报错。
shell
In general, TextStyle.lerp only works well when both TextStyles have the same "inherit" value, and
specify the same fields.
If the TextStyles were directly created by you, consider bringing them to parity to ensure a smooth
transition.
If one of the TextStyles being lerped is significantly more elaborate than the other, and has
"inherited" set to false, it is often because it is merged with another TextStyle before being
lerped. Comparing the "debugLabel"s of the two TextStyles may help identify if that was the case.
For example, you may see this error message when trying to lerp between "ThemeData()" and
"Theme.of(context)". This is because TextStyles from "Theme.of(context)" are merged with TextStyles
from another theme and thus are more elaborate than the TextStyles from "ThemeData()" (which is
reflected in their "debugLabel"s -- TextStyles from "Theme.of(context)" should have labels in the
form of "(<A TextStyle>).merge(<Another TextStyle>)"). It is recommended to only lerp ThemeData with
matching TextStyles.
例如全局设置了TextStyle的backgroundColor,fontSize
dart
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4))),
textStyle: const TextStyle(
fontSize: 24,
backgroundColor: Colors.red
)
),
)
那么,你必须在设置单个ElevatedButton的textStyle样式时使用相同的属性,如下
dart
ElevatedButton(
onPressed: () {},
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.black),
textStyle: MaterialStateProperty.all(
const TextStyle(
fontSize: 24.0,
backgroundColor: Colors.red
)
)
),
child: const Text('My Button'),
),
上面的报错只会存在于热启动中,如果你重新启动应用就不会报错。
MaterialStateProperty
ButtonStyle
的属性都是MaterialStateProperty
类型,这究竟是什么,为何不直接使用对于的Colors
,double
等类型?关于这些问题,StackOverflow上有一篇很好的回答:stackoverflow.com/questions/6...
MaterialStateProperty
作用在于能方便简单地根据按钮的不同状态来设置某个属性的不同值。按钮一般有up
,over
,down
几个状态,如果你要分别设置每个状态下的背景色,之前在Adobe Flex
中我们需要分别设置upSkin
,overSkin
、downSkin
几个属性,但Flutter不需要这么麻烦。
resolveWith
MaterialStateProperty
提供了一个resolveWith(MaterialPropertyResolver callback)
静态方法,该方法的callback
有一个states
参数,它会返回按钮当前状态的一个集合(按钮的当前状态可能不止一个),我们只需要取出对应状态设置对应的值即可。
dart
typedef MaterialPropertyResolver<T> = T Function(Set<MaterialState> states);
static MaterialStateProperty<T> resolveWith<T>(MaterialPropertyResolver<T> callback) => _MaterialStatePropertyWith<T>(callback);
先看官方的一个例子
dart
@override
Widget build(BuildContext context) {
Color getColor(Set<MaterialState> states) {
const Set<MaterialState> interactiveStates = <MaterialState>{
MaterialState.pressed,
MaterialState.hovered,
MaterialState.focused,
};
if (states.any(interactiveStates.contains)) {
return Colors.blue;
}
return Colors.red;
}
return TextButton(
style: ButtonStyle(
foregroundColor: MaterialStateProperty.resolveWith(getColor),
),
onPressed: () {},
child: const Text('TextButton'),
);
}
interactiveStates
定义了一个需要处理的状态集合,这里我们只想设置pressed
、hovered
和focused
三个状态的颜色。states.any(interactiveStates.contains)
的作用是判断states
集合中是否至少有一个元素存在于interactiveStates
集合中,如果存在返回true
,否则false
。
MaterialState
定义了很多不同的状态,除了上面的三个外,还有:dragged
、selected
、scrolledUnder
(当与可滚动的内容有重叠时)、disabled
、error
(无效状态,表单中的组件?)
all
静态方法MaterialStateProperty.all
会设置所有所有状态为同一个值,如
dart
ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.red)
)
例子
MaterialStateProperty可以给某个样式属性在不同的状态设置不同值,如果你同时设置多个样式属性,且设置相同的状态,那就可以设置相同的状态下,不同样式属性表现不一样,如下代码
dart
ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.resolveWith((states) {
if (states.contains(MaterialState.disabled)) {
return Colors.grey;
}
if (states.contains(MaterialState.pressed)) {
return Colors.green;
}
return Colors.blue;
}),
textStyle: MaterialStateProperty.resolveWith((states) {
if (states.contains(MaterialState.pressed)) {
return const TextStyle(fontSize: 40);
}
return const TextStyle(fontSize: 20);
})
)
)
常用样式属性说明
textStyle
- 用于设置按钮文本样式,文本在按钮中默认居中。
backgroundColor
- 设置按钮的背景色,文本也有背景色,该背景色是占满整个按钮。
foregroundColor
- 设置子组件的文本颜色,如果子组件是icon,那就是设置Icon的颜色。
overlayColor
- 设置按钮的focused,hovered或者pressed状态的颜色。
shadowColor
- 设置按钮的阴影颜色。
elevation
- 设置阴影的大小。
padding
- 内边距,即按钮边框与子组件之间的距离。
minimumSize
- 设置按钮的最小宽高。
maximumSize
- 设置按钮的最大宽高。
fixedSize
- 设置按钮的固定宽高,按钮不再随环境变化大小。
side
- 设置按钮的边框大小与颜色。
shape
- 设置按钮的外形,启用M3后,默认是跑到形状的。
visualDensity
- 设置按钮布局的紧凑程度。
tapTargetSize
- 指定按钮可点击的区域大小。
animationDuration
- shape与elevation变化时的动画时长。
enableFeedback
- 按钮交互时是否有提示,如点击时发声。
alignment
- 子组件的对齐方式。
splashFactory
- 水波纹的设置。
总结
全局设置ElevatedButton
的样式,可以通过MaterialApp
的theme
来设置。
单个ElevatedButton
的样式,可以直接设置其style
属性,设置style
有两种方式一种是通过ElevatedButton
的静态方法styleFrom
来设置,这个设置某个样式属性是不区分按钮状态的,比如背景色,如果你设置为红色,那么按钮的pressed
、hovered
等状态都是这个颜色;另一种方式是直接创建一个ButtonStyle
实例来设置style。
ButtonStyle
中的样式属性都是MaterialStateProperty
类型,它的作用是让开发者能方便清晰的根据按钮不同的状态来设置不同的样式。
上面的设置样式方法同样适用于TextButton
、OutlinedButton
。