【Flutter 专题】62 图解基本 Button 按钮小结 (二)
- 2019 年 10 月 7 日
- 筆記
和尚继续尝试 Flutter 的基本按钮;今天和尚学习 MaterialButton 系列相关 Button;该系列以 MaterialButton 为父类,衍生出 RaisedButton 凸起按钮,FlatButton 扁平按钮和 OutlineButton 边框按钮;可根据不同场景灵活运用;

MaterialButton
源码分析
const MaterialButton({ Key key, @required this.onPressed, this.onHighlightChanged, // 高亮变化的回调 this.textTheme, // 文字主题 this.textColor, // 文字颜色 this.disabledTextColor, // 不可点击时文字颜色 this.color, // 背景色 this.disabledColor, // 不可点击时背景色 this.highlightColor, // 点击高亮时背景色 this.splashColor, // 水波纹颜色 this.colorBrightness, this.elevation, // 阴影高度 this.highlightElevation, // 高亮时阴影高度 this.disabledElevation, // 不可点击时阴影高度 this.padding, // 内容周围边距 this.shape, // 按钮样式 this.clipBehavior = Clip.none, // 抗锯齿剪切效果 this.materialTapTargetSize, // 点击目标的最小尺寸 this.animationDuration, // 动画效果持续时长 this.minWidth, // 最小宽度 this.height, // 按钮高度 this.child, })
分析源码可知,MaterialButton 作为其他 Button 父类,各属性比较清晰明了,有 hight 属性可设置 Button 高度,其子类 Button 只可通过 padding 或其他方式调整高度;
案例尝试
和尚测试发现 hight 可以设置 MaterialButton 高度,但 shape 按钮形状却不适用;其父类 RawMaterialButton 却正常;和尚尝试网上大神的处理方式是外层依赖 Material 并需要 clip 裁切成 shape 样式;有待进一步学习;
Material( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30.0)), clipBehavior: Clip.antiAlias, child: MaterialButton( color: Colors.teal.withOpacity(0.4), height: 60.0, child: Text('MaterialButton'), onPressed: () {}))

RaisedButton / FlatButton
源码分析
const RaisedButton({ Key key, @required VoidCallback onPressed, ValueChanged<bool> onHighlightChanged, ButtonTextTheme textTheme, // 按钮文字主题 Color textColor, // 子元素颜色 Color disabledTextColor, // 不可点击时子元素颜色 Color color, // 按钮背景色 Color disabledColor, // 不可点击时按钮背景色 Color highlightColor, // 点击高亮时按钮背景色 Color splashColor, // 水波纹颜色 Brightness colorBrightness, // 颜色对比度 double elevation, // 阴影高度 double highlightElevation, // 高亮时阴影高度 double disabledElevation, // 不可点击时阴影高度 EdgeInsetsGeometry padding, // 子元素周围边距 ShapeBorder shape, // 按钮样式 Clip clipBehavior = Clip.none, // 抗锯齿剪切效果 MaterialTapTargetSize materialTapTargetSize, Duration animationDuration, // 动画时长 Widget child, }) const FlatButton({ Key key, @required VoidCallback onPressed, ValueChanged<bool> onHighlightChanged, ButtonTextTheme textTheme, // 按钮文字主题 Color textColor, // 子元素颜色 Color disabledTextColor, // 不可点击时子元素颜色 Color color, // 按钮背景色 Color disabledColor, // 不可点击时按钮背景色 Color highlightColor, // 点击高亮时按钮背景色 Color splashColor, // 水波纹颜色 Brightness colorBrightness, // 颜色对比度 EdgeInsetsGeometry padding, // 子元素周围边距 ShapeBorder shape, // 按钮样式 Clip clipBehavior = Clip.none, // 抗锯齿剪切效果 MaterialTapTargetSize materialTapTargetSize, @required Widget child, })
分析源码可知,RaisedButton 与 FlatButton 基本完全相同,只是 RaisedButton 多了一些阴影高度的特有属性,和尚准备同时对两类 Button 进行尝试,比较两者的不同;
案例尝试
- 和尚首先尝试最基本的 RaisedButton / FlatButton 可点击和不可点击样式;
// 可点击 RaisedButton(child: Text('RaisedButton'), onPressed: () => Toast.show('RaisedButton', context, duration: Toast.LENGTH_SHORT, gravity: Toast.BOTTOM)) FlatButton(child: Text('FlatButton'), onPressed: () => Toast.show('FlatButton', context, duration: Toast.LENGTH_SHORT, gravity: Toast.BOTTOM)) // 不可点击 RaisedButton(child: Text('RaisedButton'), onPressed: null) FlatButton(child: Text('FlatButton'), onPressed: null)

- ButtonTextTheme 为默认子元素主题,可以设置基本的三种主题样式:nomal 对应 [ThemeData.brightness];primary 对应 [ThemeData.primaryColor];accent 对应 [ThemeData.accentColor];展示效果比较明显;
RaisedButton(child: Text('R.nomal'), textTheme: ButtonTextTheme.normal, onPressed: () => Toast.show('RaisedButton', context, duration: Toast.LENGTH_SHORT, gravity: Toast.BOTTOM)) RaisedButton(child: Text('R.primary'), textTheme: ButtonTextTheme.primary, onPressed: () => Toast.show('RaisedButton', context, duration: Toast.LENGTH_SHORT, gravity: Toast.BOTTOM)) RaisedButton(child: Text('R.accent'), textTheme: ButtonTextTheme.accent, onPressed: () => Toast.show('RaisedButton', context, duration: Toast.LENGTH_SHORT, gravity: Toast.BOTTOM)) FlatButton(child: Text('F.nomal'), textTheme: ButtonTextTheme.normal, onPressed: () => Toast.show('FlatButton', context, duration: Toast.LENGTH_SHORT, gravity: Toast.BOTTOM)) FlatButton(child: Text('F.primary'), textTheme: ButtonTextTheme.primary, onPressed: () => Toast.show('FlatButton', context, duration: Toast.LENGTH_SHORT, gravity: Toast.BOTTOM)) FlatButton(child: Text('F.accent'), textTheme: ButtonTextTheme.accent, onPressed: () => Toast.show('FlatButton', context, duration: Toast.LENGTH_SHORT, gravity: Toast.BOTTOM)) OutlineButton(child: Text('O.nomal'), textTheme: ButtonTextTheme.normal, onPressed: () => Toast.show('OutlineButton', context, duration: Toast.LENGTH_SHORT, gravity: Toast.BOTTOM)) OutlineButton(child: Text('O.primary'), textTheme: ButtonTextTheme.primary, onPressed: () => Toast.show('OutlineButton', context, duration: Toast.LENGTH_SHORT, gravity: Toast.BOTTOM)) OutlineButton(child: Text('O.accent'), textTheme: ButtonTextTheme.accent, onPressed: () => Toast.show('OutlineButton', context, duration: Toast.LENGTH_SHORT, gravity: Toast.BOTTOM))

- textColor 为子 Widget 中元素颜色,不仅为文字颜色;disabledTextColor 为不可点击时子 Widget 元素颜色;splashColor 为点击时水波纹颜色;
// 可点击 RaisedButton(child: Row(mainAxisSize: MainAxisSize.min, children: <Widget>[ Padding(child: Icon(Icons.ac_unit), padding: EdgeInsets.only(right: 10.0)), Text('RaisedButton') ]), textColor: Colors.deepPurple, onPressed: () => {}) FlatButton(child: Row(mainAxisSize: MainAxisSize.min, children: <Widget>[ Padding(child: Icon(Icons.ac_unit), padding: EdgeInsets.only(right: 10.0)), Text('FlatButton') ]), textColor: Colors.deepPurple, onPressed: () => {}) // 不可点击 RaisedButton(child: Row(mainAxisSize: MainAxisSize.min, children: <Widget>[ Padding(child: Icon(Icons.ac_unit), padding: EdgeInsets.only(right: 10.0)), Text('RaisedButton') ]), textColor: Colors.deepPurple, onPressed: null) FlatButton(child: Row(mainAxisSize: MainAxisSize.min, children: <Widget>[ Padding(child: Icon(Icons.ac_unit), padding: EdgeInsets.only(right: 10.0)), Text('FlatButton') ]), textColor: Colors.deepPurple, onPressed: null)

- color 为 Button 背景色;highlightColor 为点击时高亮背景色;disabledColor 为不可点击时背景色;
// 可点击 RaisedButton(child: Text('RaisedButton'), onPressed: () => {}, color: Colors.green.withOpacity(0.4), highlightColor: Colors.purple.withOpacity(0.4), splashColor: Colors.yellow.withOpacity(0.7)) FlatButton(child: Text('FlatButton'), onPressed: () => {}, color: Colors.green.withOpacity(0.4), highlightColor: Colors.purple.withOpacity(0.4), splashColor: Colors.yellow.withOpacity(0.7)) // 不可点击 RaisedButton(child: Text('RaisedButton'), onPressed: null, disabledColor: Colors.red.withOpacity(0.4)) FlatButton(child: Text('FlatButton'), onPressed: null, disabledColor: Colors.red.withOpacity(0.4),)

- shape 为 Button 形状;因按钮没有 Material 中 hight 属性,需要采用 padding 或外层依赖其他 Widget 调整按钮大小;
RaisedButton(child: Text('RaisedButton'), onPressed: () => {} padding: EdgeInsets.all(16.0), shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(30.0)))) FlatButton(child: Text('FlatButton'), onPressed: () => {} padding: EdgeInsets.all(16.0), shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(30.0))))

- colorBrightness 代表颜色对比度,一般分为 light / dark 两种;一般时深色的背景需要浅色的文字对比,浅色的背景需要深色的文字对比;
// 可点击 RaisedButton(child: Text('R.light'), colorBrightness: Brightness.light, onPressed: () => {}) RaisedButton(child: Text('R.dark'), colorBrightness: Brightness.dark, onPressed: () => {}) FlatButton(child: Text('F.light'), colorBrightness: Brightness.light, onPressed: () => {}) FlatButton(child: Text('F.dark'), colorBrightness: Brightness.dark, onPressed: () => {}) // 不可点击 RaisedButton(child: Text('R.light'), colorBrightness: Brightness.light, onPressed: null) RaisedButton(child: Text('R.dark'), colorBrightness: Brightness.dark, onPressed: null) FlatButton(child: Text('F.light'), colorBrightness: Brightness.light, onPressed: null) FlatButton(child: Text('F.dark'), colorBrightness: Brightness.dark, onPressed: null)

- RaisedButton / FlatButton 均提供了 .icon 带图标的简单方式,icon / label 两个属性是必须属性;注意,.icon 方式中 RaisedButton 没有 padding 属性;
RaisedButton.icon(icon: Icon(Icons.ac_unit), label: Text('RaisedButton'), shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(30.0))), onPressed: () => {}) FlatButton.icon(icon: Icon(Icons.ac_unit), label: Text('FlatButton'), padding: EdgeInsets.all(16.0), shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(30.0))), onPressed: () => {})

- elevation 为 RaisedButton 所特有的阴影高度;highlightElevation 为高亮时阴影高度;disabledColor 为不可点击时阴影高度;
RaisedButton(child: Text('阴影'), elevation: 20.0, onPressed: () => {}) RaisedButton(child: Text('阴影'), elevation: 0.0, highlightElevation: 20.0, onPressed: () => {}) RaisedButton(child: Text('阴影'), disabledElevation: 20.0, onPressed: null)

OutlineButton
源码分析
const OutlineButton({ Key key, @required VoidCallback onPressed, ButtonTextTheme textTheme, // 按钮文字主题 Color textColor, // 文字颜色 Color disabledTextColor, // 不可点击时文字颜色 Color color, // 按钮背景色 Color highlightColor, // 高亮时颜色 Color splashColor, // 水波纹颜色 double highlightElevation, // 高亮时阴影高度 this.borderSide, // 边框样式 this.disabledBorderColor, // 不可点击时边框颜色 this.highlightedBorderColor, // 高亮时边框颜色 EdgeInsetsGeometry padding, // 内容周围边距 ShapeBorder shape, // 按钮样式 Clip clipBehavior = Clip.none, // 抗锯齿剪切效果 Widget child, })
分析源码可知,OutlineButton 与其他两种按钮略有不同,强调边框的样式属性且无长按的 tooltip 属性;
案例尝试
- 和尚首先尝试一个最基本的 OutlineButton;长按无提醒;
OutlineButton(child: Text('OutlineButton'), onPressed: () => Toast.show('OutlineButton', context, duration: Toast.LENGTH_SHORT, gravity: Toast.BOTTOM))

- 和尚尝试与其他按钮相同的几类按钮属性,使用方式相同;
OutlineButton( child: Row(mainAxisSize: MainAxisSize.min, children: <Widget>[ Padding(child: Icon(Icons.ac_unit), padding: EdgeInsets.only(right: 10.0)), Text('OutlineButton') ]), textColor: Colors.pink, disabledTextColor: Colors.green, padding: EdgeInsets.all(20.0), color: Colors.blueAccent.withOpacity(0.2), highlightColor: Colors.amber, borderSide: BorderSide(width: 4.0), highlightedBorderColor: Colors.brown, disabledBorderColor: Colors.greenAccent, shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(50.0))), clipBehavior: Clip.none, onPressed: () => Toast.show('OutlineButton', context, duration: Toast.LENGTH_SHORT, gravity: Toast.BOTTOM))

- 以下为 OutlineButton 特有属性:borderSide 代表边框样式;disabledBorderColor 代表不可点击时边框颜色;highlightedBorderColor 代表高亮时边框颜色;其中 borderSide 可以设置边框颜色宽度及样式(solid / none);
OutlineButton(child: Text('OutlineButton'), borderSide: BorderSide(width: 4.0, color: Colors.deepPurple, style: BorderStyle.solid), highlightedBorderColor: Colors.teal, onPressed: () => Toast.show('OutlineButton', context, duration: Toast.LENGTH_SHORT, gravity: Toast.BOTTOM)) OutlineButton(child: Text('OutlineButton'), borderSide: BorderSide(width: 4.0, color: Colors.deepPurple, style: BorderStyle.solid), disabledBorderColor: Colors.redAccent, onPressed: null)

- OutlineButton 还提供了 .icon 带图标的简单方式,icon / label 两个属性是必须属性;
OutlineButton.icon( icon: Icon(Icons.ac_unit), label: Text('OutlineButton'), textColor: Colors.pink, disabledTextColor: Colors.green, padding: EdgeInsets.all(20.0), color: Colors.blueAccent.withOpacity(0.2), highlightColor: Colors.amber, borderSide: BorderSide(width: 4.0), highlightedBorderColor: Colors.brown, disabledBorderColor: Colors.greenAccent, shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(50.0))), clipBehavior: Clip.none, onPressed: () => Toast.show('OutlineButton', context, duration: Toast.LENGTH_SHORT, gravity: Toast.BOTTOM))

扩展
1. textColor 的作用?
和尚原来以为按钮的子元素是 Widget,可自由设置各类效果,单独的 textColor 是否会略显多余;可实际并非如此,子元素设置颜色等之后 textColor 不生效;但 textColor 与主题相关;和尚以 OutlineButton 为例,一目了然;
// Text 设置颜色 OutlineButton( child: Text('OutlineButton', style: TextStyle(color: Colors.deepPurple)), onPressed: () => Toast.show('OutlineButton', context, duration: Toast.LENGTH_SHORT, gravity: Toast.BOTTOM)) // textColor 设置颜色 OutlineButton( child: Text('OutlineButton'), textColor: Colors.deepPurple, onPressed: () => Toast.show('OutlineButton', context, duration: Toast.LENGTH_SHORT, gravity: Toast.BOTTOM))

2. 阴影如何改颜色?
使用 RaisedButton 时会自带阴影效果,阴影的高度和高亮时的阴影高度均可自由设置;但是阴影的颜色应该如何处理呢,官方暂未提供阴影效果属性;和尚尝试了网上大神的方式,RaisedButton 外层依赖带模糊阴影效果的 Container;和尚借鉴并稍微调整一下,解决方案并非最佳,仅作尝试;
初始时定义一个默认的高度 height 作为阴影高度,监听按钮的 onHighlightChanged 方法更改 height 高度作为高亮时阴影高度;
建议: a. 使用高亮颜色时 highlightElevation 建议设置为 0.0; b. 若按钮有样式设置,依赖的 Container 也要设置相同的 shape 样式;
var height = 5.0; Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(30.0), boxShadow: <BoxShadow>[ BoxShadow( color: Colors.red.withOpacity(0.2), blurRadius: hight, offset: Offset(0, hight)) ]), child: RaisedButton( child: Text('RaisedButton'), padding: EdgeInsets.symmetric(vertical: 15.0), highlightElevation: 0.0, onHighlightChanged: (state) { setState(() { hight = (state) ? 20.0 : 5.0; }); }, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30.0)), onPressed: () => {}))
