likes
comments
collection
share

设置ElevatedButton的样式

作者站长头像
站长
· 阅读数 20

关于ButtonStyleButton样式的设置问题,包括ElevatedButton的全局样式,单个按钮的样式设置,还有MaterialStateProperty的使用。

修改单个按钮样式

当你要修改单个ElevatedButton的样式时,可以调用静态方法styleFrom设置它的style属性,如下

ElevatedButton(
  style: ElevatedButton.styleFrom(
    backgroundColor: Colors.blue
  ),
  onPressed: () => print('clicked'),
  child: const Text('custom style for a ElevatedButton')
)

上面通过styleFrom方法只是设置了背景色,保留了其他默认的样式,ElevatedButton的默认样式有两种风格:一种是使用了M3的,调用了_ElevatedButtonDefaultsM3方法来设置的(具体看源码);一种是没有使用M3的,具体如下:

{
  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属性,如下

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的样式。

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在热启动时,做样式的过度动画,否则热启动时会报错。

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

elevatedButtonTheme: ElevatedButtonThemeData(
  style: ElevatedButton.styleFrom(
    shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4))),
    textStyle: const TextStyle(
      fontSize: 24,
      backgroundColor: Colors.red
    )
  ),
)

那么,你必须在设置单个ElevatedButton的textStyle样式时使用相同的属性,如下

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类型,这究竟是什么,为何不直接使用对于的Colorsdouble等类型?关于这些问题,StackOverflow上有一篇很好的回答:stackoverflow.com/questions/6…

MaterialStateProperty作用在于能方便简单地根据按钮的不同状态来设置某个属性的不同值。按钮一般有upoverdown几个状态,如果你要分别设置每个状态下的背景色,之前在Adobe Flex中我们需要分别设置upSkinoverSkindownSkin几个属性,但Flutter不需要这么麻烦。

resolveWith

MaterialStateProperty提供了一个resolveWith(MaterialPropertyResolver callback)静态方法,该方法的callback有一个states参数,它会返回按钮当前状态的一个集合(按钮的当前状态可能不止一个),我们只需要取出对应状态设置对应的值即可。

typedef MaterialPropertyResolver<T> = T Function(Set<MaterialState> states);

static MaterialStateProperty<T> resolveWith<T>(MaterialPropertyResolver<T> callback) => _MaterialStatePropertyWith<T>(callback);

先看官方的一个例子

@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定义了一个需要处理的状态集合,这里我们只想设置pressedhoveredfocused三个状态的颜色。states.any(interactiveStates.contains)的作用是判断states集合中是否至少有一个元素存在于interactiveStates集合中,如果存在返回true,否则false

MaterialState定义了很多不同的状态,除了上面的三个外,还有:draggedselectedscrolledUnder(当与可滚动的内容有重叠时)、disablederror(无效状态,表单中的组件?)

all

静态方法MaterialStateProperty.all会设置所有所有状态为同一个值,如

ButtonStyle(
  backgroundColor: MaterialStateProperty.all(Colors.red)
)
例子

MaterialStateProperty可以给某个样式属性在不同的状态设置不同值,如果你同时设置多个样式属性,且设置相同的状态,那就可以设置相同的状态下,不同样式属性表现不一样,如下代码

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的样式,可以通过MaterialApptheme来设置。

单个ElevatedButton的样式,可以直接设置其style属性,设置style有两种方式一种是通过ElevatedButton的静态方法styleFrom来设置,这个设置某个样式属性是不区分按钮状态的,比如背景色,如果你设置为红色,那么按钮的pressedhovered等状态都是这个颜色;另一种方式是直接创建一个ButtonStyle实例来设置style。

ButtonStyle中的样式属性都是MaterialStateProperty类型,它的作用是让开发者能方便清晰的根据按钮不同的状态来设置不同的样式。

上面的设置样式方法同样适用于TextButtonOutlinedButton