Material组件库中的按钮

image.png
Material 组件库 中提供了多种按钮组件,如:

  • RaisedButton
  • FloatingActionButton
  • IconButton
  • FlatButton
  • OutlineButton
  • PopupMenuButton
  • ButtonBar
  • 等等

它们都是直接或间接对 RawMaterialButton 组件的包装定制,所以他们大多数属性都和 RawMaterialButton 一样。在介绍各个按钮时我们先介绍其默认外观,而按钮的外观大都可以通过属性来自定义,我们在后面统一介绍这些属性。另外,所有Material 库中的按钮都有如下相同点:

  • 按下时都会有“水波动画”(又称“涟漪动画”,就是点击时按钮上会出现水波荡漾的动画)。
  • 有一个 onPressed 属性来设置点击回调,当按钮按下时会执行该回调,如果不提供该回调则按钮会处于禁用状态,禁用状态不响应用户点击。

RawMaterialButton

  1. RawMaterialButton(
  2. onPressed: () {},
  3. elevation: 0.5,
  4. fillColor: Colors.white,
  5. shape: CircleBorder(
  6. side: BorderSide(
  7. width: 1.sp,
  8. color: Color(0xff576b95),
  9. )),
  10. padding: EdgeInsets.all(4.sp),
  11. child: Icon(
  12. MyIcons.icon_line_phone,
  13. size: 24.sp,
  14. color: Color.fromRGBO(87, 107, 149, 1),
  15. ),
  16. )
  17. RawMaterialButton(
  18. onPressed: () {
  19. show_dialog(
  20. context,
  21. title: "拨打电话",
  22. onOk: () {
  23. callPhone(item.phone ?? "");
  24. },
  25. onCancle: () {},
  26. text: item.phone ?? "",
  27. );
  28. },
  29. constraints: BoxConstraints(
  30. minWidth: 20.w, minHeight: 20.w),
  31. elevation: 0.5,
  32. fillColor: Colors.white,
  33. shape: CircleBorder(
  34. side: BorderSide(
  35. width: 1.sp,
  36. color: Color(0xff576b95),
  37. )),
  38. padding: EdgeInsets.all(4.sp),
  39. child: Icon(
  40. MyIcons.icon_line_phone,
  41. size: 24.sp,
  42. color:
  43. Color.fromRGBO(87, 107, 149, 1),
  44. ),
  45. )

参数属性

  1. const RawMaterialButton({
  2. Key? key,
  3. required this.onPressed,
  4. this.onLongPress,
  5. this.onHighlightChanged,
  6. this.mouseCursor,
  7. this.textStyle,
  8. this.fillColor,
  9. this.focusColor,
  10. this.hoverColor,
  11. this.highlightColor,
  12. this.splashColor,
  13. this.elevation = 2.0,
  14. this.focusElevation = 4.0,
  15. this.hoverElevation = 4.0,
  16. this.highlightElevation = 8.0,
  17. this.disabledElevation = 0.0,
  18. this.padding = EdgeInsets.zero,
  19. this.visualDensity = VisualDensity.standard,
  20. this.constraints = const BoxConstraints(minWidth: 88.0, minHeight: 36.0),
  21. this.shape = const RoundedRectangleBorder(),
  22. this.animationDuration = kThemeChangeDuration,
  23. this.clipBehavior = Clip.none,
  24. this.focusNode,
  25. this.autofocus = false,
  26. MaterialTapTargetSize? materialTapTargetSize,
  27. this.child,
  28. this.enableFeedback = true,

MaterialButton

image.png
MaterialButton中实际上是对 RawMaterialButton 的调用

  1. @override
  2. Widget build(BuildContext context) {
  3. final ThemeData theme = Theme.of(context);
  4. final ButtonThemeData buttonTheme = ButtonTheme.of(context);
  5. return RawMaterialButton(
  6. onPressed: onPressed,
  7. onLongPress: onLongPress,
  8. enableFeedback: enableFeedback,
  9. onHighlightChanged: onHighlightChanged,
  10. mouseCursor: mouseCursor,
  11. fillColor: buttonTheme.getFillColor(this),
  12. textStyle: theme.textTheme.button!.copyWith(color: buttonTheme.getTextColor(this)),
  13. focusColor: focusColor ?? buttonTheme.getFocusColor(this),
  14. hoverColor: hoverColor ?? buttonTheme.getHoverColor(this),
  15. highlightColor: highlightColor ?? theme.highlightColor,
  16. splashColor: splashColor ?? theme.splashColor,
  17. elevation: buttonTheme.getElevation(this),
  18. focusElevation: buttonTheme.getFocusElevation(this),
  19. hoverElevation: buttonTheme.getHoverElevation(this),
  20. highlightElevation: buttonTheme.getHighlightElevation(this),
  21. padding: buttonTheme.getPadding(this),
  22. visualDensity: visualDensity ?? theme.visualDensity,
  23. constraints: buttonTheme.getConstraints(this).copyWith(
  24. minWidth: minWidth,
  25. minHeight: height,
  26. ),
  27. shape: buttonTheme.getShape(this),
  28. clipBehavior: clipBehavior,
  29. focusNode: focusNode,
  30. autofocus: autofocus,
  31. animationDuration: buttonTheme.getAnimationDuration(this),
  32. materialTapTargetSize: materialTapTargetSize ?? theme.materialTapTargetSize,
  33. disabledElevation: disabledElevation ?? 0.0,
  34. child: child,
  35. );
  36. }

MaterialButton的案例

  1. MaterialButton(
  2. minWidth: 48.w,
  3. height: 48.w,
  4. child: Image.asset(
  5. getImagePath("icon_line_flashlight"),
  6. width: 24.w,
  7. height: 24.w,
  8. color: Colors.white,
  9. ),
  10. onPressed: () {
  11. _controller.toggleTorchMode();
  12. if (lightIcon == Icons.flash_on) {
  13. lightIcon = Icons.flash_off;
  14. } else {
  15. lightIcon = Icons.flash_on;
  16. }
  17. setState(() {});
  18. });

RaisedButton

RaisedButton 即”漂浮”按钮,它默认带有阴影和灰色背景。按下后,阴影会变大,如图3-10所示:
按钮 - 图3

  1. RaisedButton(
  2. child: Text("normal"),
  3. onPressed: () {},
  4. );

参数属性

  1. (new) RaisedButton RaisedButton({Key key,
  2. void Function() onPressed,
  3. void Function() onLongPress,
  4. void Function(bool) onHighlightChanged,
  5. ButtonTextTheme textTheme,
  6. Color textColor,
  7. Color disabledTextColor,
  8. Color color,
  9. Color disabledColor,
  10. Color focusColor,
  11. Color hoverColor,
  12. Color highlightColor,
  13. Color splashColor,
  14. Brightness colorBrightness,
  15. double elevation,
  16. double focusElevation,
  17. double hoverElevation,
  18. double highlightElevation,
  19. double disabledElevation,
  20. EdgeInsetsGeometry padding,
  21. ShapeBorder shape,
  22. Clip clipBehavior = Clip.none,
  23. FocusNode focusNode,
  24. bool autofocus = false,
  25. MaterialTapTargetSize materialTapTargetSize,
  26. Duration animationDuration,
  27. Widget child})

FlatButton

FlatButton 即扁平按钮,默认背景透明并不带阴影。按下后,会有背景色,如图3-11所示:
按钮 - 图4

  1. FlatButton(
  2. child: Text("normal"),
  3. onPressed: () {},
  4. )

参数属性

  1. (new) FlatButton FlatButton({Key key,
  2. void Function() onPressed,
  3. void Function() onLongPress,
  4. void Function(bool) onHighlightChanged,
  5. ButtonTextTheme textTheme,
  6. Color textColor,
  7. Color disabledTextColor,
  8. Color color,
  9. Color disabledColor,
  10. Color focusColor,
  11. Color hoverColor,
  12. Color highlightColor,
  13. Color splashColor,
  14. Brightness colorBrightness,
  15. EdgeInsetsGeometry padding,
  16. ShapeBorder shape,
  17. Clip clipBehavior = Clip.none,
  18. FocusNode focusNode,
  19. bool autofocus = false,
  20. MaterialTapTargetSize materialTapTargetSize,
  21. Widget child})

OutlineButton

OutlineButton 默认有一个边框,不带阴影且背景透明。按下后,边框颜色会变亮、同时出现背景和阴影(较弱),如图3-12所示:
按钮 - 图5

  1. OutlineButton(
  2. child: Text("normal"),
  3. onPressed: () {},
  4. )

参数属性

  1. (new) OutlineButton OutlineButton({Key key,
  2. void Function() onPressed,
  3. void Function() onLongPress,
  4. ButtonTextTheme textTheme,
  5. Color textColor,
  6. Color disabledTextColor,
  7. Color color,
  8. Color focusColor,
  9. Color hoverColor,
  10. Color highlightColor,
  11. Color splashColor,
  12. double highlightElevation,
  13. BorderSide borderSide,
  14. Color disabledBorderColor,
  15. Color highlightedBorderColor,
  16. EdgeInsetsGeometry padding,
  17. ShapeBorder shape,
  18. Clip clipBehavior = Clip.none,
  19. FocusNode focusNode,
  20. bool autofocus = false,
  21. Widget child})

IconButton

IconButton是一个可点击的Icon,不包括文字,默认没有背景,点击后会出现背景,如图3-13所示:
按钮 - 图6
代码如下:

  1. IconButton(
  2. icon: Icon(Icons.thumb_up),
  3. onPressed: () {},
  4. )

参数属性

  1. (new) IconButton IconButton({Key key,
  2. double iconSize = 24.0,
  3. EdgeInsetsGeometry padding = const EdgeInsets.all(8.0),
  4. AlignmentGeometry alignment = Alignment.center,
  5. Widget icon,
  6. Color color,
  7. Color focusColor,
  8. Color hoverColor,
  9. Color highlightColor,
  10. Color splashColor,
  11. Color disabledColor,
  12. void Function() onPressed,
  13. FocusNode focusNode,
  14. bool autofocus = false,
  15. String tooltip,
  16. bool enableFeedback = true})

TextButton

image.png

  1. abstract class ButtonStyleButton extends StatefulWidget {
  2. /// Abstract const constructor. This constructor enables subclasses to provide
  3. /// const constructors so that they can be used in const expressions.
  4. const ButtonStyleButton({
  5. Key? key,
  6. required this.onPressed,
  7. required this.onLongPress,
  8. required this.style,
  9. required this.focusNode,
  10. required this.autofocus,
  11. required this.clipBehavior,
  12. required this.child,
  13. }) : assert(autofocus != null),
  14. assert(clipBehavior != null),
  15. super(key: key);

参数属性

  1. const TextButton({
  2. Key? key,
  3. required VoidCallback? onPressed,
  4. VoidCallback? onLongPress,
  5. ButtonStyle? style,
  6. FocusNode? focusNode,
  7. bool autofocus = false,
  8. Clip clipBehavior = Clip.none,
  9. required Widget child,
  10. })

TextButton使用案例

  1. TextButton(
  2. onPressed: () {
  3. if (!_isCanClick) return;
  4. this.handlecheckPartsPrice(vm, context);
  5. },
  6. style: ButtonStyle(
  7. // alignment: Alignment.centerRight,
  8. // padding: MaterialStateProperty.all(EdgeInsets.zero), // 消除按钮带来的内边距
  9. ),
  10. child: Container(
  11. width: 160.w,
  12. height: 44.w,
  13. alignment: Alignment.center,
  14. // 垂直水平居中可以在 设置【padding】 和 【alignment、width、height】 二选一
  15. // padding: EdgeInsets.symmetric(horizontal: 23.w, vertical: 10.w),
  16. decoration: BoxDecoration(
  17. border: Border.all(
  18. color: Color(_isCanClick ? 0xFF576B95 : 0x26000000),
  19. width: 1.w,
  20. style: BorderStyle.solid,
  21. ),
  22. borderRadius: BorderRadius.circular(22.w),
  23. ),
  24. child: Text(
  25. "查询零售价",
  26. style: TextStyle(
  27. color: Color(_isCanClick ? 0xFF576B95 : 0x26000000),
  28. fontWeight: FontWeight.w600,
  29. fontSize: 16.sp,
  30. ),
  31. ),
  32. ),
  33. )
  1. TextButton(
  2. style: ButtonStyle(
  3. padding: MaterialStateProperty.all(
  4. EdgeInsets.zero)),
  5. onPressed: () {},
  6. child: item.statusName == 0
  7. ? Text(
  8. item.statusName ?? "",
  9. style: TextStyle(
  10. color: Color(0xff576b95),
  11. fontWeight: FontWeight.w600,
  12. fontSize: 14.sp),
  13. )
  14. : Container(
  15. width: 80.w,
  16. height: 32.w,
  17. margin: EdgeInsets.all(0),
  18. alignment: Alignment.center,
  19. // color: Colors.white,
  20. decoration: BoxDecoration(
  21. color: Color.fromRGBO(
  22. 56, 109, 248, 1),
  23. borderRadius:
  24. BorderRadius.circular(
  25. 16.w)),
  26. child: Text(
  27. item.statusName ?? "",
  28. style: TextStyle(
  29. color: Colors.white,
  30. fontWeight:
  31. FontWeight.w600,
  32. fontSize: 14.sp),
  33. ),
  34. )
  1. TextButton(
  2. // style: ButtonStyle(
  3. // padding: MaterialStateProperty.all(
  4. // EdgeInsets.zero)),
  5. onPressed: () {
  6. switch (item.orderStatus) {
  7. case "12881001":
  8. show_dialog(context,
  9. title: "提示",
  10. text: "确认借车?", onOk: () {
  11. vm2.handleReturnCar(
  12. item.orderNo, 1);
  13. }, onCancle: () {});
  14. break;
  15. }
  16. },
  17. child: item.orderStatus == "12881001" ||
  18. item.orderStatus == "12881003"
  19. ? Container(
  20. // width: 80.w,
  21. // height: 32.w,
  22. constraints: BoxConstraints(
  23. maxWidth:double.infinity, //宽度尽可能大
  24. // minWidth: 80.w,
  25. ),
  26. margin: EdgeInsets.all(0),
  27. padding: EdgeInsets.symmetric(
  28. vertical: 8.sp,
  29. horizontal: 12.sp),
  30. // alignment: Alignment.center,
  31. // color: Colors.white,
  32. decoration: BoxDecoration(
  33. color: Color.fromRGBO(
  34. 56, 109, 248, 1),
  35. borderRadius:
  36. BorderRadius.circular(
  37. 16.w)),
  38. child: Text(
  39. item.statusName ?? "",
  40. style: TextStyle(
  41. color: Colors.white,
  42. fontWeight: FontWeight.w600,
  43. fontSize: 14.sp),
  44. ),
  45. )
  46. : Text(
  47. item.statusName ?? "",
  48. style: TextStyle(
  49. color: Color(0xff576b95),
  50. fontWeight: FontWeight.w600,
  51. fontSize: 14.sp),
  52. ),
  53. )
 //这是一个文本按钮 未设置点击事件下的样式
  Widget buildTextButton() {
    return TextButton(
      child: Text("TextButton按钮"),
      //添加一个点击事件
      onPressed: () {},
      //设置按钮是否自动获取焦点
      autofocus: true,
      //定义一下文本样式
      style: ButtonStyle(
        //定义文本的样式 这里设置的颜色是不起作用的
        textStyle: MaterialStateProperty.all(
            TextStyle(fontSize: 18, color: Colors.red)),
        //设置按钮上字体与图标的颜色
        //foregroundColor: MaterialStateProperty.all(Colors.deepPurple),
        //更优美的方式来设置
        foregroundColor: MaterialStateProperty.resolveWith(
          (states) {
            if (states.contains(MaterialState.focused) &&
                !states.contains(MaterialState.pressed)) {
              //获取焦点时的颜色
              return Colors.blue;
            } else if (states.contains(MaterialState.pressed)) {
              //按下时的颜色
              return Colors.deepPurple;
            }
            //默认状态使用灰色
            return Colors.grey;
          },
        ),
        //背景颜色
        backgroundColor: MaterialStateProperty.resolveWith((states) {
          //设置按下时的背景颜色
          if (states.contains(MaterialState.pressed)) {
            return Colors.blue[200];
          }
          //默认不使用背景颜色
          return null;
        }),
        //设置水波纹颜色
        overlayColor: MaterialStateProperty.all(Colors.yellow),
        //设置阴影  不适用于这里的TextButton
        elevation: MaterialStateProperty.all(0),
        //设置按钮内边距
        padding: MaterialStateProperty.all(EdgeInsets.all(10)),
        //设置按钮的大小
        minimumSize: MaterialStateProperty.all(Size(200, 100)),

        //设置边框
        side:
            MaterialStateProperty.all(BorderSide(color: Colors.grey, width: 1)),
        //外边框装饰 会覆盖 side 配置的样式
        shape: MaterialStateProperty.all(StadiumBorder()),
      ),
    );
  }

带图标的按钮

RaisedButton、FlatButton、OutlineButton都有一个icon 构造函数,通过它可以轻松创建带图标的按钮,如图3-14所示:
按钮 - 图8

RaisedButton.icon(
  icon: Icon(Icons.send),
  label: Text("发送"),
  onPressed: _onPressed,
),
OutlineButton.icon(
  icon: Icon(Icons.add),
  label: Text("添加"),
  onPressed: _onPressed,
),
FlatButton.icon(
  icon: Icon(Icons.info),
  label: Text("详情"),
  onPressed: _onPressed,
),

参数属性

(new) RaisedButton RaisedButton.icon({Key key,
 void Function() onPressed,
 void Function() onLongPress,
 void Function(bool) onHighlightChanged,
 ButtonTextTheme textTheme,
 Color textColor,
 Color disabledTextColor,
 Color color,
 Color disabledColor,
 Color focusColor,
 Color hoverColor,
 Color highlightColor,
 Color splashColor,
 Brightness colorBrightness,
 double elevation,
 double highlightElevation,
 double disabledElevation,
 ShapeBorder shape,
 Clip clipBehavior,
 FocusNode focusNode,
 bool autofocus,
 MaterialTapTargetSize materialTapTargetSize,
 Duration animationDuration,
 Widget icon,
 Widget label})

自定义按钮外观

按钮外观可以通过其属性来定义,不同按钮属性大同小异,我们以FlatButton为例,介绍一下常见的按钮属性,详细的信息可以查看API文档。

const FlatButton({
  ...  
  @required this.onPressed, //按钮点击回调
  this.textColor, //按钮文字颜色
  this.disabledTextColor, //按钮禁用时的文字颜色
  this.color, //按钮背景颜色
  this.disabledColor,//按钮禁用时的背景颜色
  this.highlightColor, //按钮按下时的背景颜色
  this.splashColor, //点击时,水波动画中水波的颜色
  this.colorBrightness,//按钮主题,默认是浅色主题 
  this.padding, //按钮的填充
  this.shape, //外形
  @required this.child, //按钮的内容
})

其中大多数属性名都是自解释的,我们不赘述。下面我们通过一个示例来看看如何自定义按钮。

蓝色背景的圆角矩形边框按钮

FlatButton(
  color: Colors.blue,
  highlightColor: Colors.blue[700],
  colorBrightness: Brightness.dark,
  splashColor: Colors.grey,
  child: Text("Submit"),
  shape:RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)),
  onPressed: () {},
)

很简单吧,在上面的代码中,我们主要通过 shape 来指定其外形为一个圆角矩形。

  • 因为按钮背景是蓝色(深色),我们需要指定按钮主题colorBrightness为Brightness.dark,这是为了保证按钮文字颜色为浅色。
  • Flutter 中没有提供去除背景的设置,假若我们需要去除背景,则可以通过将背景颜色设置为全透明来实现。对应上面的代码,便是将 color: Colors.blue 替换为 color: Color(0x000000)。

细心的读者可能会发现这个按钮没有阴影(点击之后也没有),这样会显得没有质感。其实这也很容易,将上面的FlatButton换成RaisedButton就行,其它代码不用改(这里 color 也不做更改),换了之后的效果如图3-16所示:
按钮 - 图9
是不是有质感了!之所以会这样,是因为RaisedButton默认有配置阴影:

const RaisedButton({
  ...
  this.elevation = 2.0, //正常状态下的阴影
  this.highlightElevation = 8.0,//按下时的阴影
  this.disabledElevation = 0.0,// 禁用时的阴影
  ...
}

值得注意的是,在Material 组件库中,我们会在很多组件中见到elevation相关的属性,它们都是用来控制阴影的,这是因为阴影在Material设计风格中是一种很重要的表现形式,以后在介绍其它组件时,便不再赘述。

如果我们想实现一个背景渐变的圆角按钮,按钮有没有相应的属性呢?
答案是没有,但是,我们可以通过其它方式来实现,我们将在后面”自定义组件”一章中实现。

flutter 中 TextButton、OutlinedButton、ElevatedButton去除内置padding

Text(
    '*****Flutter 1.22版本新增的按钮*****',
    style: TextStyle(color: Colors.redAccent),
),
SizedBox(height: 20),
Container(
    color: Colors.orange,
    child: TextButton(
        onPressed: () {},
        child: Text('TextButton'),
        style: ButtonStyle(
            tapTargetSize: MaterialTapTargetSize.shrinkWrap, // 设置点击区域尺寸跟随内容大小
            minimumSize: MaterialStateProperty.all(Size(0, 0)),
            padding: MaterialStateProperty.all(EdgeInsets.zero),
        ),
    ),
),
SizedBox(height: 20),
Container(
    color: Colors.blue,
    child: OutlinedButton(
        child: Text('OutlinedButton'),
        onPressed: () {},
        style: ButtonStyle(
            backgroundColor: MaterialStateProperty.all(Colors.redAccent),
            minimumSize: MaterialStateProperty.all(Size(0, 0)),
            padding: MaterialStateProperty.all(EdgeInsets.zero),
        ),
    ),
),
SizedBox(height: 20),
Container(
    color: Colors.pinkAccent,
    child: ElevatedButton(
        child: Text('ElevatedButton'),
        onPressed: () {},
        style: ButtonStyle(
            minimumSize: MaterialStateProperty.all(Size(0, 0)),
            padding: MaterialStateProperty.all(EdgeInsets.zero),
        ),
    ),
),