2019-04-04 用 flutter 教你自定义一个 checkbox

前言

最近在撸flutter代码的时候发现CheckBox不是那么好用,一直想是否可以跟Android的CheckBox一样可以设置文字在右边,看了网上的代码都是用组件嵌套在里面,所以决定写一个自定义个不用嵌套使用简单的CheckBox

思路

首先说一下实现一个CheckBox我们需要画一个空心圆、一个实心圆和一行文字,再增加一个点击事件即可实现简单的CheckeBox

编码

1、创建一个CheckRenderRenderBox

我这里创建RenderBox的时候就定义了一下组件的大小为14,先不去计算组件的大小。

import 'package:flutter/material.dart';
import 'dart:math' as Math;

class CheckRender extends LeafRenderObjectWidget {
  CheckRender({Key key}) : super(key: key);

  @override
  RenderObject createRenderObject(BuildContext context) => _CheckedRender();
}

class _CheckedRender extends RenderBox {
  final _paint = Paint();
  
  _CheckedRender() {
    _paint.color = Colors.white; //白色笔
    _paint.isAntiAlias = true; // 抗锯齿
  }

  @override
  void performLayout() {
    double width = Math.max(constraints.minWidth, 14.0);
    double height = Math.max(constraints.minHeight, 14.0);
   // 最终宽度
    size = Size(width, height);
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    super.paint(context, offset);
    Canvas canvas = context.canvas;
  }
}
2、在paint函数中画一个空心圆和实心圆

(1)计算空心圆的中心点和半径出来,我们上面就给组价设置过高度和宽度

 double _center = size.height / 2;//中心点
 double _radius = _center - _center * 0.12 / 2;//半径

(2)设置paint的style为空心及线条宽度

_paint.style = PaintingStyle.stroke; // 设置空心笔
_paint.strokeWidth = height * 0.12;// 设置空心圆的宽度

(3)绘制空心圆(canvas在上面已经定义了)

Offset centerH = Offset(offset.dx + _center, offset.dy + _center); //中心位置
Rect arcRect = Rect.fromCircle(center: centerH, radius: _radius);//范围大小
canvas.drawArc(arcRect, 0.0, 360, false, _paint);//画出空心圆

(4)绘制实心圆

// 给paint的style设置为实心
_paint.style = PaintingStyle.fill;
// 画中间的圆圈
Rect arcRectCenter = Rect.fromCircle(center: centerH, radius: _radius / 1.8);
// 画实心圆
canvas.drawArc(arcRectCenter, 0.0, 360, false, _paint);

《2019-04-04 用 flutter 教你自定义一个 checkbox》 实现效果图

3、绘制文字

fluttercanvas中画文字的方法是drawParagraph,在ParagraphBuilder可以设置很多样式我们这次用不到,这次只是画个文字在上面显示就行。

1、创建一个Paragraph对象

var paragraph = ui.ParagraphBuilder(ui.ParagraphStyle(fontSize: 14))
    ..pushStyle(ui.TextStyle(color: Colors.white))
    ..addText('CheckBox');
    
Paragraph _paragraph = paragraph.build()
  ..layout(ui.ParagraphConstraints(width: double.infinity));

2、重新计算组件宽度performLayout

@override
void performLayout() {
    double width = Math.max(constraints.minWidth, 14.0);
    double height = Math.max(constraints.minHeight, 14.0);
    // 最终宽度
    size = Size(width + _paragraph.minIntrinsicWidth, height);
}

3、画出文字

canvas.drawParagraph(_paragraph, Offset(centerH.dx + 5.5 + _center, offset.dy + (size.height + _paragraph.height) / 2));

《2019-04-04 用 flutter 教你自定义一个 checkbox》 CheckBox图

4、增加状态切换

1、处理事件分发
_CheckedRender里面添加

@override
  bool hitTest(HitTestResult result, {ui.Offset position}) {
    if(size.contains(position)) {
      result.add(BoxHitTestEntry(this, position));
      return true;
    }
    return false;
  }

2、创建一个CheckTextBox继承StatefulWidget
继承一个带状态的widget并且用GestureDetector包住CheckRender

class CheckTextBox extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _CheckTextBox();
}

class _CheckTextBox extends State<CheckTextBox> {
  // 是否是check状态
  bool _isChecked = false;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      child: CheckRender(),
      onTap: () => setState(() {_isChecked = !_isChecked;}),
    );
  }
}

不想写了…………完整代码贴出来。

完整代码

import 'dart:math' as Math;
import 'dart:ui' as ui;
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';

class CheckTextBox extends StatefulWidget {
  // 是否是check状态
  final bool isChecked;

  // check文本
  final String text;

  // check回传
  final ValueChanged<bool> onChecked;

  CheckTextBox(this.text, {Key key, this.isChecked: false, this.onChecked})
      : super(key: key);

  @override
  State<StatefulWidget> createState() => _CheckTextBox(isChecked: isChecked);
}

class _CheckTextBox extends State<CheckTextBox> {
  // 是否是check状态
  bool isChecked;

  _CheckTextBox({this.isChecked: false});

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      child: CheckRender(isChecked: isChecked, text: widget.text),
      onTap: () => setState(() {
            isChecked = !isChecked;
            // 回传改变事件
            if (widget.onChecked != null) widget.onChecked(isChecked);
          }),
    );
  }
}

class CheckRender extends LeafRenderObjectWidget {
  final bool isChecked;
  final String text;

  CheckRender({Key key, this.text, this.isChecked}) : super(key: key);

  @override
  RenderObject createRenderObject(BuildContext context) =>
      _CheckedRender(checked: isChecked, text: text);

  @override
  void updateRenderObject(BuildContext context, _CheckedRender renderObject) {
    renderObject.isChecked = isChecked;
    renderObject.text = text;
  }
}

class _CheckedRender extends RenderBox {
  final _paint = Paint();
  ui.Paragraph _paragraph;
  bool _checked = false;
  String _text = '';

  _CheckedRender({checked: false, text}) {
    _checked = checked;
    _text = text;
    _paint.color = Color(0xffffffff); //白色笔
    _paint.isAntiAlias = true; // 抗锯齿
    resetText();
  }

  @override
  void performLayout() {
    double width = Math.max(constraints.minWidth, 14.0);
    double height = Math.max(constraints.minHeight, 14.0);
    if (_paragraph != null) {
      width += _paragraph.minIntrinsicWidth + 5.5;
    }
    // 最终宽度
    size = Size(width, height);
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    super.paint(context, offset);
    Canvas canvas = context.canvas;

    double _center = size.height / 2; //中心点
    double _radius = _center - _center * 0.12 / 2; //半径
    _paint.style = PaintingStyle.stroke; // 设置空心笔
    _paint.strokeWidth = size.height * 0.12; // 设置空心圆的宽度

    Offset centerH = Offset(offset.dx + _center, offset.dy + _center); //中心位置
    Rect arcRect = Rect.fromCircle(center: centerH, radius: _radius); //范围大小
    canvas.drawArc(arcRect, 0.0, 360, false, _paint); //画出空心圆

    // 判断是否选中
    if (_checked) {
      // 设置实心圆
      _paint.style = PaintingStyle.fill;
      // 画中间的圆圈
      Rect arcRectCenter =
          Rect.fromCircle(center: centerH, radius: _radius / 1.8);
      canvas.drawArc(arcRectCenter, 0.0, 360, false, _paint);
    }

    if (_paragraph != null) {
      // 画出文字
      canvas.drawParagraph(
          _paragraph,
          Offset(centerH.dx + _center + 5.5,
              offset.dy + (size.height - _paragraph.height) / 2));
    }
  }

  @override
  bool hitTest(HitTestResult result, {ui.Offset position}) {
    if (size.contains(position)) {
      result.add(BoxHitTestEntry(this, position));
      return true;
    }
    return false;
  }

  @override
  bool hitTestSelf(Offset position) => true;

  void resetText() {
    var paragraph = ui.ParagraphBuilder(ui.ParagraphStyle(fontSize: 14))
      ..pushStyle(ui.TextStyle(color: Color(0xffffffff)))
      ..addText(_text);

    _paragraph = paragraph.build()
      ..layout(ui.ParagraphConstraints(width: double.infinity));
  }

  set isChecked(bool newValue) {
    if (newValue != _checked) {
      _checked = newValue;
      markNeedsPaint();
    }
  }

  set text(String newValue) {
    if (newValue != _text) {
      _text = newValue;
      resetText();
      markNeedsPaint();
    }
  }
}

    原文作者:deadcalm
    原文地址: https://www.jianshu.com/p/5cb1381f6dec
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞