【Flutter 专题】73 图解自定义 ACECheckBox 复选框

举报
阿策小和尚 发表于 2022/02/24 09:22:29 2022/02/24
【摘要】 0 基础学习 Flutter,第七十三步:尝试自定义 ACECheckBox 多选框!

      CheckBox 复选框对于所有的开发朋友并不陌生,Flutter 提供了简单便捷的使用方法,但针对不同的业务场景,可能会有些许的不同,例如圆角矩形替换为圆形,复选框尺寸调整等;
      小菜今天通过对 CheckBox 进行研究扩展实现如下功能的 自定义 ACECheckBox 复选框;

  1. 复选框可变更未选中状态颜色;
  2. 复选框支持圆形样式;
  3. 复选框支持自定义尺寸;

CheckBox

源码分析

const Checkbox({
    Key key,
    @required this.value,       // 复选框状态 true/false/null
    this.tristate = false,      // 是否为三态
    @required this.onChanged,   // 状态变更回调
    this.activeColor,           // 选中状态填充颜色
    this.checkColor,            // 选中状态对号颜色
    this.materialTapTargetSize, // 点击范围
})

      分析源码可知,tristatetrue 时复选框有三种状态;为 falsevalue 不可为 null

案例尝试

return Checkbox( value: state, onChanged: (value) => setState(() => state = value));

return Checkbox(value: state, checkColor: Colors.purpleAccent.withOpacity(0.7),
      onChanged: (value) => setState(() => state = value));

return Checkbox(value: state, activeColor: Colors.teal.withOpacity(0.3), checkColor: Colors.purpleAccent.withOpacity(0.7),
      onChanged: (value) => setState(() => state = value));

return Checkbox(tristate: true, value: _triState == null ? _triState : state, 
      activeColor: Colors.teal.withOpacity(0.3), checkColor: Colors.purpleAccent.withOpacity(0.7),
      onChanged: (value) => setState(() {
            if (value == null) {
              _triState = value;
            } else {
              _triState = ''; state = value;
            }
          }));
}

ACECheckBox

扩展一:变更未选中颜色

源码分析
// CheckBox
inactiveColor: widget.onChanged != null ? themeData.unselectedWidgetColor : themeData.disabledColor,
// ACECheckBox
inactiveColor: widget.onChanged != null
    ? widget.unCheckColor ?? themeData.unselectedWidgetColor
    : themeData.disabledColor,

      分析 CheckBox 源码,其中复选框未选中颜色通过 ThemeData.unselectedWidgetColor 设置,修改颜色成本较大,小菜添加了 unCheckColor 属性,可自由设置未选中状态颜色,未设置时默认为 ThemeData.unselectedWidgetColor

案例尝试
return ACECheckbox(value: aceState, unCheckColor: Colors.amberAccent, onChanged: (value) => setState(() => aceState = value));

return ACECheckbox(value: aceState, checkColor: Colors.red.withOpacity(0.7),
      unCheckColor: Colors.amberAccent, onChanged: (value) => setState(() => aceState = value));

return ACECheckbox(value: aceState, activeColor: Colors.indigoAccent.withOpacity(0.3), checkColor: Colors.red.withOpacity(0.7),
      unCheckColor: Colors.amberAccent, onChanged: (value) => setState(() => aceState = value));

return ACECheckbox(tristate: true, value: _triAceState == null ? _triAceState : aceState,
      activeColor: Colors.indigoAccent.withOpacity(0.7), checkColor: Colors.red.withOpacity(0.4),
      unCheckColor: Colors.amberAccent, onChanged: (value) {
        setState(() {
          if (value == null) {
            _triAceState = value;
          } else {
            _triAceState = ''; aceState = value;
          }
        });
      });

扩展二:添加圆形样式

源码分析
// 绘制边框
_drawBorder(canvas, outer, t, offset, type, paint) {
  assert(t >= 0.0 && t <= 0.5);
  final double size = outer.width;
  if ((type ?? ACECheckBoxType.normal) == ACECheckBoxType.normal) {
    canvas.drawDRRect(
        outer, outer.deflate(math.min(size / 2.0, _kStrokeWidth + size * t)),
        paint..strokeWidth = _kStrokeWidth / 2.0..style = PaintingStyle.fill);
  } else {
    canvas.drawCircle(
        Offset(offset.dx + size / 2.0, offset.dy + size / 2.0), size / 2.0,
        paint..strokeWidth = _kStrokeWidth..style = PaintingStyle.stroke);
  }
}
// 绘制填充
_drawInner(canvas, outer, offset, type, paint) {
  if ((type ?? ACECheckBoxType.normal) == ACECheckBoxType.normal) {
    canvas.drawRRect(outer, paint);
  } else {
    canvas.drawCircle(
        Offset(offset.dx + outer.width / 2.0, offset.dy + outer.width / 2.0),
        outer.width / 2.0, paint);
  }
}

      分析源码可知,CheckBox 边框和内部填充以及对号全是通过 Canvas 进行绘制,其中绘制边框时,采用双层圆角矩形方式 drawDRRect,默认两层圆角矩形之间是填充方式;小菜添加 ACECheckBoxType 属性,允许用户设置圆角样式;
      绘制边框时画笔属性要与 drawDRRect 进行区分;其中复选框边框和内部填充两部分需要进行样式判断;

案例尝试
return ACECheckbox(value: aceState, unCheckColor: Colors.amberAccent, type: ACECheckBoxType.circle, onChanged: (value) => setState(() => aceState = value));

return ACECheckbox(value: aceState, checkColor: Colors.red.withOpacity(0.7),
      unCheckColor: Colors.amberAccent, type: ACECheckBoxType.circle,
      onChanged: (value) => setState(() => aceState = value));

return ACECheckbox(
      value: aceState, activeColor: Colors.indigoAccent.withOpacity(0.3), checkColor: Colors.red.withOpacity(0.7),
      unCheckColor: Colors.amberAccent, type: ACECheckBoxType.circle,
      onChanged: (value) => setState(() => aceState = value));

return ACECheckbox(tristate: true, value: _triAceState == null ? _triAceState : aceState,
      activeColor: Colors.indigoAccent.withOpacity(0.7), checkColor: Colors.red.withOpacity(0.4),
      unCheckColor: Colors.amberAccent, type: ACECheckBoxType.circle,
      onChanged: (value) {
        setState(() {
          if (value == null) {
            _triAceState = value;
          } else {
            _triAceState = ''; aceState = value;
          }
        });
      });

扩展三:自定义尺寸

源码分析
@override
void paint(PaintingContext context, Offset offset) {
  final Canvas canvas = context.canvas;
  paintRadialReaction(canvas, offset, size.center(Offset.zero));
  
  final Paint strokePaint = _createStrokePaint(checkColor);
  final Offset origin = offset + (size / 2.0 - Size.square(width) / 2.0);
  final AnimationStatus status = position.status;
  final double tNormalized = status == AnimationStatus.forward || status == AnimationStatus.completed ? position.value : 1.0 - position.value;
  if (_oldValue == false || value == false) {
    final double t = value == false ? 1.0 - tNormalized : tNormalized;
    final RRect outer = _outerRectAt(origin, t);
    final Paint paint = Paint()..color = _colorAt(t);
    if (t <= 0.5) {
      _drawBorder(canvas, outer, t, origin, type, paint);
    } else {
      _drawInner(canvas, outer, origin, type, paint);
      final double tShrink = (t - 0.5) * 2.0;
      if (_oldValue == null || value == null)
        _drawDash(canvas, origin, tShrink, width, strokePaint);
      else
        _drawCheck(canvas, origin, tShrink, width, strokePaint);
    }
  } else {
    final RRect outer = _outerRectAt(origin, 1.0);
    final Paint paint = Paint()..color = _colorAt(1.0);
    _drawInner(canvas, outer, origin, type, paint);
    if (tNormalized <= 0.5) {
      final double tShrink = 1.0 - tNormalized * 2.0;
      if (_oldValue == true)
        _drawCheck(canvas, origin, tShrink, width, strokePaint);
      else
        _drawDash(canvas, origin, tShrink, width, strokePaint);
    } else {
      final double tExpand = (tNormalized - 0.5) * 2.0;
      if (value == true)
        _drawCheck(canvas, origin, tExpand, width, strokePaint);
      else
        _drawDash(canvas, origin, tExpand, width, strokePaint);
    }
  }
}

      分析源码 CheckBox 尺寸是固定的 Checkbox.width = 18.0,无法调整尺寸,小菜添加一个 width 参数,默认为 18.0 允许用户按需调整尺寸;如上是绘制复选框的三态情况;

案例尝试
return ACECheckbox(value: aceState, width: 10.0, onChanged: (value) => setState(() => aceState = value));

return ACECheckbox(value: aceState, checkColor: Colors.red.withOpacity(0.7), width: 18.0,
      onChanged: (value) => setState(() => aceState = value));

return ACECheckbox(value: aceState, activeColor: Colors.indigoAccent.withOpacity(0.3), checkColor: Colors.red.withOpacity(0.7),
      width: 28.0, onChanged: (value) => setState(() => aceState = value));

return ACECheckbox(tristate: true, value: _triAceState == null ? _triAceState : aceState,
      activeColor: Colors.indigoAccent.withOpacity(0.7), checkColor: Colors.red.withOpacity(0.4),
      type: ACECheckBoxType.normal, width: 38.0, onChanged: (value) {
        setState(() {
          if (value == null) {
            _triAceState = value;
          } else {
            _triAceState = ''; aceState = value;
          }
        });
      });


      ACECheckBox 源码


      小菜在扩展过程中,学习 CheckBox 源码,还有很多有意思的地方,包括对 true/false/null 三态的处理方式,以及 .lerp 动画效果的应用,在实际应用中都很有帮助;
      小菜自定义 ACECheckBox 的扩展还不够完善,目前暂未添加图片或 Icon 的样式,以后有机会一同扩展;如有错误请多多指导!

来源: 阿策小和尚

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。