**首先效果上图:

注意开头的时候,动态增加了4条数据,柱子和线都变了,如果你们用websocket或者tcp传输不间断数据,就动态了。 now,let‘s go。
分为三节:
笔者最开始画k图一脸懵逼,使用横向ListView提供滑动事件,利用GestureDe提供双指缩放事件,最后因为在计算k线宽度上完全崩溃。
然后github了一下,有个人画了个的静态图当时有200多个star,现在我又去找,发现已经不见了。
我仔细阅读了他的代码,发现他比我简单的多,也就几百行代码,完全使用CustomPainter画出来的,而且避开了复杂的计算,仅仅只用维护一个数据集就可以,非常方便集成,于是我用了他的思想和部分算法,我在它的基础上画了时间轴,数字,日线,等各种,非常感谢,部分代码已用到项目中,他写的类叫OHLCVGraph,我虽然沿用了这个类名,但是被我改的面目全非,如果你们搜索到了他的代码,请帮我给她一个star。
先看后端数据接口
int id = dataMap['t'][i]; //时间戳
double open = dataMap['o'][i]+0.001;//开 盘
double high = dataMap['h'][i]+0.001;//最高
double low = dataMap['l'][i]+0.001;//最低
double close = dataMap['c'][i]+0.001;//收盘
double volumeto = 5000 + (Random().nextInt(5000)) +0.001;//交易 量
我提供的数据中没有交 易 量这一条,我使用随机数代替,值5000-10000之间。 加0.001是将数据全部转换成double,数据源中存在int,我记得之间试着使用父类num,后来出错了,不知道为啥。
思想:首先你得画外边框,即底边的线(标时间刻度的线),和右边的线(标价格的线),其次你得根据控件的高度设置比例,用以计算每个值所在的纵坐标。同理,根据控件的宽度算出时间的比例,用以计算每个横坐标。最高和最低用线表示,开和收用实心矩形表示。
找到数据集中的最大值和最小值,用于和当前控件高度计算出比例,比例巨重要。
for (int i = 0; i < preData.length; i++) {
if (preData[i]["high"] > _max) {
_max = preData[i]["high"].toDouble();
}
if (preData[i]["low"] < _min) {
_min = preData[i]["low"].toDouble();
}
if (preData[i]["volumeto"] > _maxVolume) {
_maxVolume = preData[i]["volumeto"].toDouble();
}
}
画水平线和刻度,gridLineAmount即画几条水平线,水平线的右侧标上价格度。垂直线同理,标上时间刻度
for (int i = 0; i < gridLineAmount; i++) {
// 画平线
gridLineValue = _max - (((_max - _min) / (gridLineAmount - 1)) * i);
//画文字
gridLineTextPainters.add(new TextPainter(
text: new TextSpan(
text: gridLineText,
style: new TextStyle(
color: gridLineLabelColor,
fontSize: 6.0,
fontWeight: FontWeight.bold)),
textDirection: TextDirection.ltr));
//此处并没有绘制文字,只是测量,flutter中要花文字必须先测量。此处相当于做好准备工作后放入集合,以便使用
gridLineTextPainters[i].layout();
}
// 画横线
for (int i = 0; i < gridLineAmount; i++) {
gridLineY = (gridLineDist * i).round().toDouble();
canvas.drawLine(new Offset(0.0, gridLineY),
new Offset(width, gridLineY), gridPaint);
// 水平字 此处真正绘制文字
gridLineTextPainters[i]
.paint(canvas, new Offset(width + 2.0, gridLineY - 6.0));
}
接下来计算柱子,比例很重要
final double heightNormalizer = height / (_max - _min); //计算高度比例
final double rectWidth = width / data.length; //计算宽度比例
这里画柱子,计算出左上右下。
for (int i = 0; i < data.length; i++) {
记录坐标中心,用于长按事件快速定位,此处使用二分法。请看第三节
herizontalLocalValues.add(i * rectWidth + rectWidth / 2);
rectLeft = (i * rectWidth) + lineWidth / 2;
rectRight = ((i + 1) * rectWidth) - lineWidth / 2;
//判断用什么颜色,红涨绿跌或涨红跌
if (data[i]["open"] > data[i]["close"]) {
rectTop = height - (data[i]["open"] - _min) * heightNormalizer;
rectBottom = height - (data[i]["close"] - _min) * heightNormalizer;
rectPaint = new Paint()
..color = decreaseColor
..strokeWidth = lineWidth;
Rect ocRect =
new Rect.fromLTRB(rectLeft, rectTop, rectRight, rectBottom);
canvas.drawRect(ocRect, rectPaint);
}
}
//high和low的线
double low = height - (data[i]["low"] - _min) * heightNormalizer;
double high = height - (data[i]["high"] - _min) * heightNormalizer;
canvas.drawLine(
new Offset(rectLeft + rectWidth / 2 - lineWidth / 2, rectBottom),
new Offset(rectLeft + rectWidth / 2 - lineWidth / 2, low),
rectPaint);
canvas.drawLine(
new Offset(rectLeft + rectWidth / 2 - lineWidth / 2, rectTop),
new Offset(rectLeft + rectWidth / 2 - lineWidth / 2, high),
rectPaint);
关键代码就这些,详细看源码。如果看得懂,请看下节。