前言
最近在撸flutter代码的时候发现CheckBox
不是那么好用,一直想是否可以跟Android的CheckBox
一样可以设置文字在右边,看了网上的代码都是用组件嵌套在里面,所以决定写一个自定义个不用嵌套使用简单的CheckBox
思路
首先说一下实现一个CheckBox
我们需要画一个空心圆、一个实心圆和一行文字,再增加一个点击事件即可实现简单的CheckeBox
。
编码
1、创建一个CheckRender
和RenderBox
我这里创建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);
3、绘制文字
在flutter
的canvas
中画文字的方法是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));
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();
}
}
}