Flutter绘制k线图——第一节单页静态K线图

940 阅读3分钟

**首先效果上图:

再上代码

注意开头的时候,动态增加了4条数据,柱子和线都变了,如果你们用websocket或者tcp传输不间断数据,就动态了。 now,let‘s go。

分为三节:

1、完成静态单页面K线图

2、增加水平滑动和双指缩放

3、增加当前值水平线,最后的柱子跟随变化,长按事件

笔者最开始画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);

关键代码就这些,详细看源码。如果看得懂,请看下节。