Flutter--Button浅谈

290 阅读3分钟

作为一个常用到不能再常用的组件,material库中的button组件都有一点奇怪——存在无法设置的内外边距,我们做一个简单的展示,选几个常见button,统一使用Text做child,外加一个带红色的Container组件,观察margin和padding.

你会发现,除了InkWell,即使padding设置为0,仍然无法消除消除上下padding,这其实不是padding属性没有生效,而是这些按钮有默认大小的约束。这点可以通过源码和不设置child的UI来佐证。

MaterialButton设置它的高度属性,看源码的使用,是修改高度约束,但是小到一定程度还是无法修改,是因为有别的地方进一步作出了限制,这就是我觉得奇怪的一些地方。这会给使用者带来不便,所以我设计基础组件的基本原则之一就是不要去干涉外界的使用,比如我做了一个Card组件,我就不会给它一个默认margin
MaterialButton(
  onPressed: () {

  },
  // child: Text("MaterialButton"),
  padding: EdgeInsets.all(0),
  height: 10,
)

其实上面所有的button都是对Inkwell的进一步封装,它包含点击特效(水波纹),以及各种手势识别,而它的本质也是在GestureDetector的基础上加了一个动画层,所以如果不需要动画效果,你大可以直接使用GestureDetector。进阶一点的做法就是基于这两者来创建你自己的Button。

InkWell的build部分源码(ink_well.dart)

这时你可能会觉得直接使用InkWell不就好了,假如你把它放在一个container里面,并且container具有背景色,你会发现水波纹效果消失了,你需要再套上一个Material组件,水波纹才会出现,这时你又回发现,这个组件自己具有形状背景色,你还得去消除,是不是很蛋疼。所以最终我是自己做了一个带有点击效果的Button组件,默认支持按钮禁用,背景色,大小,圆角等常用属性设置,供大家参考

import 'package:flutter/material.dart';

class UIButton extends StatefulWidget {

  final Widget? child;

  final bool disabled;

  final double? width;
  final double? height;

  final EdgeInsetsGeometry? padding;

  final Color? backgroundColor;
  final Color? disabledColor;
  final BorderRadiusGeometry? borderRadius;
  //大部分时间只需要统一四角圆角, 和borderRadius二选一
  final double? allRadius;

  final GestureTapCallback? onTap;
  final GestureTapCallback? onDoubleTap;
  final GestureLongPressCallback? onLongPress;
  final GestureTapCancelCallback? onTapCancel;

  final Clip? clipBehavior;

  UIButton({
    Key? key,
    this.child,
    this.disabled = false,
    this.width,
    this.height,
    this.padding,
    this.borderRadius,
    this.allRadius,
    this.backgroundColor,
    this.disabledColor,
    this.onTap,
    this.onDoubleTap,
    this.onLongPress,
    this.onTapCancel,
    this.clipBehavior
  }) : assert(allRadius == null || borderRadius == null), super(key: key);

  @override
  State<StatefulWidget> createState() => _UIButton();

}

class _UIButton extends State<UIButton> {

  bool _isTap = false;

  @override
  Widget build(BuildContext context) {
    BorderRadiusGeometry radiusGeometry = widget.borderRadius ?? BorderRadius.all(Radius.circular(widget.allRadius ?? 0));

    return GestureDetector(
      child: Container(
        child: Center(child: widget.child,),
        width: widget.width,
        height: widget.height,
        clipBehavior: widget.clipBehavior ?? Clip.none,
        foregroundDecoration: _isTap || widget.disabled ? BoxDecoration(
          color: widget.disabledColor ?? Color(0x85ffffff),
          borderRadius: radiusGeometry
        ) : null,
        decoration: BoxDecoration(
          borderRadius: radiusGeometry,
          color: widget.backgroundColor
        ),
        padding: widget.padding,
      ),
      onTap: widget.disabled ? null : widget.onTap,
      onDoubleTap: widget.disabled ? null : widget.onDoubleTap,
      onLongPress: widget.disabled ? null : widget.onLongPress,
      onTapUp: (TapUpDetails details) {
        refreshTapState(false);
      },
      onLongPressEnd: (LongPressEndDetails details) {
        refreshTapState(false);
      },
      onTapDown: (TapDownDetails details) {
        refreshTapState(true);
      },
      onTapCancel: () {
        //TODO:会导致长按时自动移除前景色
        refreshTapState(false);
        if (widget.onTapCancel != null) widget.onTapCancel!();
      },
    );
  }

  void refreshTapState(bool state) {
    setState(() {
      _isTap = state;
    });
  }
}

 测试的代码

Column(children: [
        SizedBox(width: double.infinity,),

        addSpacer(),

        InkWell(child: Text("InkWell"), onTap: () {

        },).addRedBackground(),

        addSpacer(),

        TextButton(
          onPressed: () {}, 
          child: Text("TextButton"),
          style: ButtonStyle(
            padding: MaterialStateProperty.all(EdgeInsets.zero)
          ),
        ).addRedBackground(),

        addSpacer(),

        MaterialButton(
          onPressed: () {

          },
          child: Text("MaterialButton"),
          padding: EdgeInsets.all(0),
        ).addRedBackground(),
        
        addSpacer(),
        
        OutlinedButton(
            onPressed: () {},
            child: Text("OutlinedButton"),
          style: ButtonStyle(
            padding: MaterialStateProperty.all(EdgeInsets.zero),
          ),
        ).addRedBackground(),
      ],
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.center,
      ),


//空button
Column(children: [
        SizedBox(width: double.infinity,),

        addSpacer(),

        InkWell(child: Text(""), onTap: () {

        },).addRedBackground(),

        addSpacer(),

        TextButton(
          onPressed: () {}, 
          child: Text(""),
          style: ButtonStyle(
            padding: MaterialStateProperty.all(EdgeInsets.zero)
          ),
        ).addRedBackground(),

        addSpacer(),

        MaterialButton(
          onPressed: () {

          },
          // child: Text("MaterialButton"),
          padding: EdgeInsets.all(0),
        ).addRedBackground(),
        
        addSpacer(),
        
        OutlinedButton(
            onPressed: () {},
            child: Text(""),
          style: ButtonStyle(
            padding: MaterialStateProperty.all(EdgeInsets.zero),
          ),
        ).addRedBackground(),
      ],
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.center,
      )