新按钮和按钮主题
摘要
#Flutter 添加了一套新的基础材质按钮部件和主题。原始类已弃用,并最终会被移除。总目标是使按钮更灵活,并通过构造函数参数或主题更容易配置。
FlatButton
、RaisedButton
和 OutlineButton
部件已被分别替换为 TextButton
、ElevatedButton
和 OutlinedButton
。每个新的按钮类都有其自己的主题:TextButtonTheme
、ElevatedButtonTheme
和 OutlinedButtonTheme
。原始的 ButtonTheme
类不再使用。按钮的外观由 ButtonStyle
对象指定,而不是一大堆部件参数和属性。这大致类似于使用 TextStyle
对象定义文本外观的方式。新的按钮主题也通过 ButtonStyle
对象进行配置。ButtonStyle
本身只是一个视觉属性的集合。许多这些属性都是使用 MaterialStateProperty
定义的,这意味着它们的值可以取决于按钮的状态。
上下文
#我们引入了新的替代按钮部件和主题,而不是尝试就地演化现有的按钮类及其主题。除了让我们免于就地演化现有类所带来的向后兼容性迷宫之外,新的名称还使 Flutter 与 Material Design 规范同步,该规范对按钮组件使用了新名称。
旧部件 | 旧主题 | 新部件 | 新主题 |
---|---|---|---|
FlatButton | ButtonTheme | TextButton | TextButtonTheme |
RaisedButton | ButtonTheme | ElevatedButton | ElevatedButtonTheme |
OutlineButton | ButtonTheme | OutlinedButton | OutlinedButtonTheme |
新的主题遵循 Flutter 大约一年前为新的 Material 部件采用的“标准化”模式。主题属性和部件构造函数参数默认情况下为 null。非 null 的主题属性和部件参数指定了组件默认值的覆盖。实现和记录默认值是按钮组件部件的唯一责任。默认值本身主要基于整体主题的 colorScheme
和 textTheme
。
从视觉上看,新的按钮看起来略有不同,因为它们与当前的 Material Design 规范相匹配,并且它们的颜色是根据整体主题的 ColorScheme
进行配置的。填充、圆角半径和悬停/焦点/按下反馈方面也存在其他细微差异。
许多应用程序只需将新的类名替换为旧的类名即可。使用黄金图像测试或已使用构造函数参数或原始 ButtonTheme
配置按钮外观的应用程序可能需要查阅迁移指南和后续的入门材料。
API 更改:ButtonStyle 而不是单个样式属性
#除了简单的用例之外,新按钮类的 API 与旧类不兼容。新按钮和主题的视觉属性使用单个 ButtonStyle
对象进行配置,类似于如何使用 TextStyle
对象配置 TextField
或 Text
部件。大多数 ButtonStyle
属性都是使用 MaterialStateProperty
定义的,因此单个属性可以根据按钮的按下/聚焦/悬停/等状态表示不同的值。
按钮的 ButtonStyle
不会定义按钮的视觉属性,它定义了按钮默认视觉属性的覆盖,其中默认属性由按钮部件本身计算。例如,要覆盖所有状态下 TextButton
的默认前景(文本/图标)颜色,可以编写
TextButton(
style: ButtonStyle(
foregroundColor: MaterialStateProperty.all<Color>(Colors.blue),
),
onPressed: () { },
child: Text('TextButton'),
)
这种覆盖很常见;但是,在许多情况下,还需要覆盖文本按钮用于指示其悬停/焦点/按下状态的覆盖颜色。这可以通过将 overlayColor
属性添加到 ButtonStyle
中来完成。
TextButton(
style: ButtonStyle(
foregroundColor: MaterialStateProperty.all<Color>(Colors.blue),
overlayColor: MaterialStateProperty.resolveWith<Color?>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.hovered))
return Colors.blue.withOpacity(0.04);
if (states.contains(MaterialState.focused) ||
states.contains(MaterialState.pressed))
return Colors.blue.withOpacity(0.12);
return null; // Defer to the widget's default.
},
),
),
onPressed: () { },
child: Text('TextButton')
)
颜色 MaterialStateProperty
只需要为应覆盖其默认值的颜色返回值。如果它返回 null,则将使用部件的默认值。例如,仅覆盖文本按钮的焦点覆盖颜色
TextButton(
style: ButtonStyle(
overlayColor: MaterialStateProperty.resolveWith<Color?>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.focused))
return Colors.red;
return null; // Defer to the widget's default.
}
),
),
onPressed: () { },
child: Text('TextButton'),
)
styleFrom()
ButtonStyle 工具方法
#Material Design 规范根据颜色方案的主色定义按钮的前景色和覆盖颜色。主色以不同的不透明度呈现,具体取决于按钮的状态。为了简化创建包含所有依赖于颜色方案颜色的属性的按钮样式,每个按钮类都包含一个静态 styleFrom()
方法,该方法从一组简单的值构建 ButtonStyle
,包括它所依赖的 ColorScheme
颜色。
此示例创建了一个按钮,该按钮使用指定的主题颜色和 Material Design 规范中的不透明度覆盖其前景色及其覆盖颜色。
TextButton(
style: TextButton.styleFrom(
primary: Colors.blue,
),
onPressed: () { },
child: Text('TextButton'),
)
TextButton
文档指出,按钮禁用时的前景色基于颜色方案的 onSurface
颜色。要使用 styleFrom()
覆盖它:
TextButton(
style: TextButton.styleFrom(
primary: Colors.blue,
onSurface: Colors.red,
),
onPressed: null,
child: Text('TextButton'),
)
如果您尝试创建 Material Design 变体,则使用 styleFrom()
方法是创建 ButtonStyle
的首选方法。最灵活的方法是直接定义 ButtonStyle
,并为要覆盖其外观的状态提供 MaterialStateProperty
值。
ButtonStyle 默认值
#像新的按钮类这样的部件根据整体主题的 colorScheme
和 textTheme
以及按钮的当前状态来计算其默认值。在少数情况下,它们还会考虑整体主题的颜色方案是浅色还是深色。每个按钮都具有一个受保护的方法,该方法根据需要计算其默认样式。尽管应用程序不会直接调用此方法,但其 API 文档解释了所有默认值是什么。当按钮或按钮主题指定 ButtonStyle
时,只有按钮样式的非 null 属性会覆盖计算出的默认值。按钮的 style
参数会覆盖相应按钮主题中指定的非 null 属性。例如,如果 TextButton
样式的 foregroundColor
属性非 null,则它会覆盖 TextButonTheme
样式的同一属性。
如前所述,每个按钮类都包含一个名为 styleFrom
的静态方法,该方法从一组简单的值(包括它所依赖的 ColorScheme
颜色)构建 ButtonStyle
。在许多常见情况下,使用 styleFrom
创建一个覆盖默认值的临时 ButtonStyle
最简单。当自定义样式的目标是覆盖默认样式所依赖的颜色方案颜色(如 primary
或 onPrimary
)时,尤其如此。对于其他情况,您可以直接创建 ButtonStyle
对象。这样做使您能够控制所有按钮可能的状态(如按下、悬停、禁用和聚焦)的视觉属性(如颜色)的值。
迁移指南
#使用以下信息将您的按钮迁移到新的 API。
恢复原始按钮视觉效果
#在许多情况下,只需从旧按钮类切换到新按钮类即可。假设大小/形状的细微变化以及颜色可能发生的较大变化不是问题。
为了在这些情况下保留原始按钮的外观,可以定义与原始按钮尽可能匹配的按钮样式。例如,以下样式使 TextButton
看起来像默认的 FlatButton
final ButtonStyle flatButtonStyle = TextButton.styleFrom(
primary: Colors.black87,
minimumSize: Size(88, 36),
padding: EdgeInsets.symmetric(horizontal: 16),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(2)),
),
);
TextButton(
style: flatButtonStyle,
onPressed: () { },
child: Text('Looks like a FlatButton'),
)
类似地,使 ElevatedButton
看起来像默认的 RaisedButton
final ButtonStyle raisedButtonStyle = ElevatedButton.styleFrom(
onPrimary: Colors.black87,
primary: Colors.grey[300],
minimumSize: Size(88, 36),
padding: EdgeInsets.symmetric(horizontal: 16),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(2)),
),
);
ElevatedButton(
style: raisedButtonStyle,
onPressed: () { },
child: Text('Looks like a RaisedButton'),
)
OutlinedButton
的 OutlineButton
样式稍微复杂一些,因为轮廓的颜色在按钮按下时会更改为主色。轮廓的外观由 BorderSide
定义,您将使用 MaterialStateProperty
来定义按下的轮廓颜色
final ButtonStyle outlineButtonStyle = OutlinedButton.styleFrom(
foregroundColor: Colors.black87,
minimumSize: Size(88, 36),
padding: EdgeInsets.symmetric(horizontal: 16),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(2)),
),
).copyWith(
side: MaterialStateProperty.resolveWith<BorderSide?>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.pressed)) {
return BorderSide(
color: Theme.of(context).colorScheme.primary,
width: 1,
);
}
return null;
},
),
);
OutlinedButton(
style: outlineButtonStyle,
onPressed: () { },
child: Text('Looks like an OutlineButton'),
)
要恢复整个应用程序中按钮的默认外观,可以在应用程序的主题中配置新的按钮主题
MaterialApp(
theme: ThemeData.from(colorScheme: ColorScheme.light()).copyWith(
textButtonTheme: TextButtonThemeData(style: flatButtonStyle),
elevatedButtonTheme: ElevatedButtonThemeData(style: raisedButtonStyle),
outlinedButtonTheme: OutlinedButtonThemeData(style: outlineButtonStyle),
),
)
要恢复应用程序一部分中按钮的默认外观,可以使用 TextButtonTheme
、ElevatedButtonTheme
或 OutlinedButtonTheme
包装部件子树。例如
TextButtonTheme(
data: TextButtonThemeData(style: flatButtonStyle),
child: myWidgetSubtree,
)
迁移具有自定义颜色的按钮
#以下部分介绍以下 FlatButton
、RaisedButton
和 OutlineButton
颜色参数的使用
textColor
disabledTextColor
color
disabledColor
focusColor
hoverColor
highlightColor*
splashColor
新的按钮类不支持单独的高亮颜色,因为它不再是 Material Design 的一部分。
迁移具有自定义前景和背景颜色的按钮
#原始按钮类的两种常见自定义是 FlatButton
的自定义前景色,或 RaisedButton
的自定义前景和背景颜色。使用新的按钮类产生相同的结果很简单
FlatButton(
textColor: Colors.red, // foreground
onPressed: () { },
child: Text('FlatButton with custom foreground/background'),
)
TextButton(
style: TextButton.styleFrom(
primary: Colors.red, // foreground
),
onPressed: () { },
child: Text('TextButton with custom foreground'),
)
在这种情况下,TextButton
的前景色(文本/图标)颜色及其悬停/聚焦/按下覆盖颜色将基于 Colors.red
。默认情况下,TextButton
的背景填充颜色为透明。
迁移具有自定义前景和背景颜色的 RaisedButton
RaisedButton(
color: Colors.red, // background
textColor: Colors.white, // foreground
onPressed: () { },
child: Text('RaisedButton with custom foreground/background'),
)
ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Colors.red, // background
onPrimary: Colors.white, // foreground
),
onPressed: () { },
child: Text('ElevatedButton with custom foreground/background'),
)
在这种情况下,按钮对颜色方案的主色的使用与 TextButton
相反:主色是按钮的背景填充颜色,onPrimary
是前景色(文本/图标)颜色。
迁移具有自定义覆盖颜色的按钮
#覆盖按钮的默认聚焦、悬停、高亮或水波纹颜色不太常见。FlatButton
、RaisedButton
和 OutlineButton
类具有这些状态相关颜色的单独参数。新的 TextButton
、ElevatedButton
和 OutlinedButton
类改为使用单个 MaterialStateProperty
参数。新的按钮允许指定所有颜色的状态相关值,原始按钮仅支持指定现在称为“overlayColor”的内容。
FlatButton(
focusColor: Colors.red,
hoverColor: Colors.green,
splashColor: Colors.blue,
onPressed: () { },
child: Text('FlatButton with custom overlay colors'),
)
TextButton(
style: ButtonStyle(
overlayColor: MaterialStateProperty.resolveWith<Color?>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.focused))
return Colors.red;
if (states.contains(MaterialState.hovered))
return Colors.green;
if (states.contains(MaterialState.pressed))
return Colors.blue;
return null; // Defer to the widget's default.
}),
),
onPressed: () { },
child: Text('TextButton with custom overlay colors'),
)
新版本虽然不太紧凑,但更灵活。在原始版本中,不同状态的优先级是隐式(且未记录)且固定的,在新版本中,它是显式的。对于经常指定这些颜色的应用程序,最简单的迁移路径是定义一个或多个与上述示例匹配的 ButtonStyles
- 并只使用样式参数 - 或定义一个无状态包装部件来封装三个颜色参数。
迁移具有自定义禁用颜色的按钮
#这是一个相对较少的自定义。FlatButton
、RaisedButton
和 OutlineButton
类具有 disabledTextColor
和 disabledColor
参数,它们定义按钮的 onPressed
回调为 null 时的背景和前景色。
默认情况下,所有按钮都使用颜色方案的 onSurface
颜色,禁用前景色不透明度为 0.38。只有 ElevatedButton
具有非透明背景颜色,其默认值为 onSurface
颜色,不透明度为 0.12。因此,在许多情况下,只需使用 styleFrom
方法即可覆盖禁用颜色
RaisedButton(
disabledColor: Colors.red.withOpacity(0.12),
disabledTextColor: Colors.red.withOpacity(0.38),
onPressed: null,
child: Text('RaisedButton with custom disabled colors'),
),
ElevatedButton(
style: ElevatedButton.styleFrom(onSurface: Colors.red),
onPressed: null,
child: Text('ElevatedButton with custom disabled colors'),
)
要完全控制禁用颜色,必须根据 MaterialStateProperties
显式定义 ElevatedButton
的样式
RaisedButton(
disabledColor: Colors.red,
disabledTextColor: Colors.blue,
onPressed: null,
child: Text('RaisedButton with custom disabled colors'),
)
ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.resolveWith<Color?>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.disabled))
return Colors.red;
return null; // Defer to the widget's default.
}),
foregroundColor: MaterialStateProperty.resolveWith<Color?>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.disabled))
return Colors.blue;
return null; // Defer to the widget's default.
}),
),
onPressed: null,
child: Text('ElevatedButton with custom disabled colors'),
)
与之前的情况一样,对于经常出现此迁移的应用程序,有一些明显的方法可以使新版本更紧凑。
迁移具有自定义高度的按钮
#这也是一种相对较少见的自定义。通常,只有ElevatedButton
(最初称为RaisedButtons
)包含高度变化。对于与基准高度成比例的高度(根据 Material Design 规范),可以非常简单地覆盖所有高度。
默认情况下,禁用按钮的高度为 0,其余状态相对于基准高度 2 定义。
disabled: 0
hovered or focused: baseline + 2
pressed: baseline + 6
因此,要迁移一个所有高度都已定义的RaisedButton
RaisedButton(
elevation: 2,
focusElevation: 4,
hoverElevation: 4,
highlightElevation: 8,
disabledElevation: 0,
onPressed: () { },
child: Text('RaisedButton with custom elevations'),
)
ElevatedButton(
style: ElevatedButton.styleFrom(elevation: 2),
onPressed: () { },
child: Text('ElevatedButton with custom elevations'),
)
要任意覆盖一个高度,例如按下时的高度
RaisedButton(
highlightElevation: 16,
onPressed: () { },
child: Text('RaisedButton with a custom elevation'),
)
ElevatedButton(
style: ButtonStyle(
elevation: MaterialStateProperty.resolveWith<double?>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.pressed))
return 16;
return null;
}),
),
onPressed: () { },
child: Text('ElevatedButton with a custom elevation'),
)
迁移具有自定义形状和边框的按钮
#原始的FlatButton
、RaisedButton
和OutlineButton
类都提供了一个shape参数,该参数定义了按钮的形状及其轮廓的外观。相应的新的类及其主题支持分别指定按钮的形状和其边框,使用OutlinedBorder shape
和BorderSide side
参数。
在此示例中,原始的OutlineButton
版本为其突出显示(按下)状态下的边框指定与其他状态相同的颜色。
OutlineButton(
shape: StadiumBorder(),
highlightedBorderColor: Colors.red,
borderSide: BorderSide(
width: 2,
color: Colors.red
),
onPressed: () { },
child: Text('OutlineButton with custom shape and border'),
)
OutlinedButton(
style: OutlinedButton.styleFrom(
shape: StadiumBorder(),
side: BorderSide(
width: 2,
color: Colors.red
),
),
onPressed: () { },
child: Text('OutlinedButton with custom shape and border'),
)
新的OutlinedButton
小部件的大多数样式参数,包括其形状和边框,都可以使用MaterialStateProperty
值指定,也就是说,它们可以根据按钮的状态具有不同的值。要指定按钮按下时不同的边框颜色,请执行以下操作
OutlineButton(
shape: StadiumBorder(),
highlightedBorderColor: Colors.blue,
borderSide: BorderSide(
width: 2,
color: Colors.red
),
onPressed: () { },
child: Text('OutlineButton with custom shape and border'),
)
OutlinedButton(
style: ButtonStyle(
shape: MaterialStateProperty.all<OutlinedBorder>(StadiumBorder()),
side: MaterialStateProperty.resolveWith<BorderSide>(
(Set<MaterialState> states) {
final Color color = states.contains(MaterialState.pressed)
? Colors.blue
: Colors.red;
return BorderSide(color: color, width: 2);
}
),
),
onPressed: () { },
child: Text('OutlinedButton with custom shape and border'),
)
时间轴
#包含在版本中:1.20.0-0.0.pre
稳定版:2.0.0
参考文献
#API 文档
ButtonStyle
ButtonStyleButton
ElevatedButton
ElevatedButtonTheme
ElevatedButtonThemeData
OutlinedButton
OutlinedButtonTheme
OutlinedButtonThemeData
TextButton
TextButtonTheme
TextButtonThemeData
相关 PR
除非另有说明,否则本网站上的文档反映了 Flutter 的最新稳定版本。页面上次更新于 2024-07-07。 查看源代码 或 报告问题.