UI图如下
通过Flutter中文网的电子书知道,Flutter要实现一个自定义控件,需要用到CustomPaint和CustomPainter。
CustomPaint当中有一个参数painter
这个painter就是CustomPainter
在CustomPaint中还有一个child的Widget参数,这个应该都很熟悉,就是一个子控件,这个就可以用来设置自定义控件的大小尺寸。
而CustomPainter通过源码可以知道,里面的参数并不能实现这个UI效果图的计算和传参,那么就需要自己去生成一个CustomChartPainter继承于CustomPainter,并重载shouldRepaint、shouldRebuildSemantics和paint方法,其中shouldRepaint和shouldRebuildSemantics设置分别返回true和false。
根据UI图呢,不需要x轴和y轴,但是有坐标显示,那么我们需要两个集合xAxis和yAxis,还需要展示的集合数据datas,单个柱形的宽度barWidth,y轴的刻度的个数num,那我们也把y轴刻度之间的间隔传进来gap
分别通过size.width和size.height来获取画布的宽、高,那么y轴刻度之间的间隔就应该是size.height/num,通过这几个值,就可以来计算出y轴刻度的绘制位置
首先先计算出各个刻度到画布顶部的距离,(itemH*index)是刻度的高度,距离顶部的高度的话,就应该用画布的高度减去刻度的高度
final double _top=sizeH-(itemH*index);
刻度绘制的位置应该在y轴的左侧, 并且我想让相对应的x轴的水平位置在刻度text的中间位置,那么就需要做一个偏移量的计算
final Offset textOffset =Offset(
0 -ScreenUtil().setSp(12),
_top -ScreenUtil().setSp(12) /2,
);
绘制文字,我们用TextPainter来实现
TextPainter(
text:TextSpan(
text: value,
style:TextStyle(
fontSize:ScreenUtil().setSp(12),
color: ColorUtils.getColor("#C2C2C2")),
),
textAlign: TextAlign.right,
textDirection: TextDirection.ltr,
textWidthBasis: TextWidthBasis.longestLine,
)
..layout(minWidth:0, maxWidth:ScreenUtil().scaleWidth *40)
..paint(canvas, textOffset);
这样,y轴刻度这边就绘制完成了。
接下来绘制x轴和柱状图:先定义一个Paint,
final paint =Paint()..style = PaintingStyle.fill;
计算出x轴刻度之间的距离(为什么是length-1,是因为x轴最左侧不按0刻度来做)
final itemW = size.width / (xAxis.length -1);
按y轴相同的方式把x轴绘制出来(高度+12的原因就是x刻度标识要在x轴下方)
for (var i =0; i < xAxis.length; i++) {
final xData =xAxis[i];
final xOffset =Offset(itemW * i, sh +12);
// 绘制横轴标识
TextPainter(
textAlign: TextAlign.center,
text:TextSpan(
text:'$xData',
style:TextStyle(
fontSize:ScreenUtil().setSp(14), color: ColorUtils.getColor("#C2C2C2")),
),
textDirection: TextDirection.ltr,
)
..layout(
minWidth:0,
maxWidth: size.width,
)
..paint(canvas, xOffset);
再把柱状图之间的间隔计算出来
final barGap = (size.width -barWidth *xAxis.length) / (xAxis.length -1);
按照UI图,有个底色的柱状图,然后是有一个标识数量的蓝色的柱状图,那么通过data * sh / (gap *num)来计算出柱状图的高度, i *barWidth + (i * barGap) + barGap /2来计算出柱状图的左侧x坐标
for (int i =0; i < datas.length; i++) {
final double data =datas[i];
final double top = sh - data * sh / (gap *num);
final double left = i *barWidth + (i * barGap) + barGap /2;
paint.color = ColorUtils.getColor("#EEEEEE");
final allRect =Rect.fromLTWH(left, 0, barWidth, sh);//这个就是用来绘制灰度底色的全高的柱状图
canvas.drawRect(allRect, paint);
paint.color = ColorUtils.getColor("#305FFF");
final rect =Rect.fromLTWH(left, top, barWidth, data * sh / (gap *num));
canvas.drawRect(rect, paint);
}
到这里基本上整个柱状图的计算代码就完成了(没有x、y轴)。
使用起来就方便很多了,放在CustomPaint中使用
全部代码:同时也欢迎指正和更好的方法。
import 'package:app_nh/utils/ColorUtils.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class CustomChartextends StatefulWidget {
final Listdatas;
final ListxAxis;
final ListyAxis;
CustomChart(this.datas, this.yAxis, this.xAxis);
@override
CustomChartStatecreateState() =>CustomChartState();
}
class CustomChartStateextends State {
@override
Widgetbuild(BuildContext context) {
ScreenUtil.init(context, width:375, height:667);
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(height:24 *ScreenUtil().scaleHeight),
CustomPaint(
painter:CustomChartPainter(
// 最后向 ColumnChartPainter 传入 _animations 数组
datas:widget.datas,
xAxis:widget.xAxis,
barWidth:ScreenUtil().scaleWidth *6.93,
gap:50.0,
yAxis:widget.yAxis,
num:4,
),
child:Container(
width:ScreenUtil().scaleWidth *300,
height:ScreenUtil().scaleHeight *160),
),
SizedBox(height:24 *ScreenUtil().scaleHeight),
],
);
}
}
class CustomChartPainterextends CustomPainter {
final Listdatas;
final ListxAxis;
final ListyAxis;
final barWidth;
final gap;
final num;
CustomChartPainter(
{@required this.datas,
@required this.xAxis,
@required this.yAxis,
@required this.gap,
@required this.barWidth,
@required this.num});
@override
void paint(Canvas canvas, Size size) {
_drawLabels(canvas, size);
_drawBarChart(canvas, size);
}
void _drawLabels(Canvas canvas, Size size) {
final double sizeH = size.height;
final double sizeW = size.width;
final double itemH = sizeH /num;
yAxis.asMap().forEach((index, value) {
final double _top = sizeH - (itemH * index);
final Offset textOffset =Offset(
0 -ScreenUtil().setSp(12),
_top -ScreenUtil().setSp(12) /2,
);
TextPainter(
text:TextSpan(
text: value,
style:TextStyle(
fontSize:ScreenUtil().setSp(12),
color: ColorUtils.getColor("#C2C2C2")),
),
textAlign: TextAlign.right,
textDirection: TextDirection.ltr,
textWidthBasis: TextWidthBasis.longestLine,
)
..layout(minWidth:0, maxWidth:ScreenUtil().scaleWidth *40)
..paint(canvas, textOffset);
});
}
void _drawBarChart(Canvas canvas, Size size) {
final sh = size.height;
final paint =Paint()..style = PaintingStyle.fill;
final itemW = size.width / (xAxis.length -1);
for (var i =0; i
final xData =xAxis[i];
final xOffset =Offset(itemW * i, sh +12);
// 绘制横轴标识
TextPainter(
textAlign: TextAlign.center,
text:TextSpan(
text:'$xData',
style:TextStyle(
fontSize:ScreenUtil().setSp(14),
color: ColorUtils.getColor("#C2C2C2")),
),
textDirection: TextDirection.ltr,
)
..layout(
minWidth:0,
maxWidth: size.width,
)
..paint(canvas, xOffset);
}
final barGap = (size.width -barWidth *xAxis.length) / (xAxis.length -1);
for (int i =0; i
final double data =datas[i];
final double top = sh - data * sh / (gap *num);
final double left = i *barWidth + (i * barGap) + barGap /2;
paint.color = ColorUtils.getColor("#EEEEEE");
final allRect =Rect.fromLTWH(left, 0, barWidth, sh);
canvas.drawRect(allRect, paint);
paint.color = ColorUtils.getColor("#305FFF");
final rect =Rect.fromLTWH(left, top, barWidth, data * sh / (gap *num));
canvas.drawRect(rect, paint);
}
}
@override
boolshouldRepaint(CustomPainter oldDelegate) =>true;
@override
boolshouldRebuildSemantics(CustomPainter oldDelegate) =>false;
}