Flutter自定义View : 绘制曲线统计图

4,289 阅读2分钟

在本文中,将通过的Flutter的自定义view实现曲线统计图,来学习一些有关自定义view的相关知识,举一反三,更加熟练的运用自定义view.最终效果如图.

正文开始

基本运用

若想用自定义view,首先要先建一个类并继承 CustomPainter

class BaseLineChart extends CustomPainter {
   @override
  void paint(Canvas canvas, Size size) {
    // 绘制代码
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
   return true; // [true]允许重新绘制
  }
  
}

建好之后,放在CustomPaint中即可当普通Widget使用

Container(
    child: CustomPaint(painter: BaseLineChart()),
)

绘制规则

在绘制方式上与Android原生的绘制规则基本相同,在屏幕坐标系中,以屏幕左上角作为原点,这个原点向右是X轴的正轴,向下是Y轴正轴。如下所示: XY的最大值为手机屏幕的像素值,如:1080 × 1920.所以在绘制时会更多的采用比例的方式设置长宽.

开始绘制

初始化画笔

设置为成员变量

 /// 画图的笔
  Paint painter = Paint()
    ..strokeWidth = 1.2
    ..style = PaintingStyle.stroke;

  /// 画文字的笔
  TextPainter textPainter = TextPainter(
    textDirection: TextDirection.ltr,
    maxLines: 1,
  );

设置区域,确定宽高

以父组件宽高为尺,确定出比例,规定绘制内容宽高,以确保不同手机屏幕显示的比例不变.

@override
  void paint(Canvas canvas, Size size) {
    // [size]为父组件的尺寸 ,
    // 超过父组件的绘制内容都会被剪切掉
    Rect rect = Offset.zero & size;
    canvas.clipRect(rect);
    // 设置背景颜色
    painter.color = Colors.white;
    painter.style = PaintingStyle.fill;
    canvas.drawRect(rect, painter);

    // 根据父组件尺寸,确定自定义view的宽,文字大小
    width = size.width;
    yTextwidth = width / 7;
    unitWidth = (width - yTextwidth) / 4;
    
    ...
 }

绘制背景横线

绘制横线时,按从左到右,从上到下的方式绘制.

@override
void paint(Canvas canvas, Size size) {

    ...

    painter.strokeWidth = 1.2; // 线条宽度
    painter.color = Color(0xffEBECF0); // 线段颜色
    // [LINE_NUM] 线段数量
    for (int i = 0; i < LINE_NUM - 1; i++) {
      // 绘制横线
      // drawLine('起点坐标','终点坐标')
      canvas.drawLine(Offset(yTextwidth, i * LINE_SPACE + Y_TEXT_HEIGHT), Offset(width, i * LINE_SPACE + Y_TEXT_HEIGHT), painter);
    }
    painter.strokeWidth = 1.4; // 最后一条线条较粗
    painter.color = Color(0xffC4C6CF);
    // 绘制横线
    canvas.drawLine(Offset(yTextwidth, (LINE_NUM - 1) * LINE_SPACE + Y_TEXT_HEIGHT),
        Offset(width, (LINE_NUM - 1) * LINE_SPACE + Y_TEXT_HEIGHT), painter);
    
    ...
    
}

绘制文本,X,Y值及标题

绘制文本时调用textPainter.paint(canvas,'起始坐标值'),这个方法中只有开始位置坐标,坐标值为文本的左上角,所以绘制时要考虑到文本的宽度,与高度,保证文字显示位置正常.

@override
void paint(Canvas canvas, Size size) {

    ...

    // y轴的标题
    textPainter.text = TextSpan(
      text: '文本内容',
      style: new TextStyle(
        color: Color(0xff303133),
        fontSize: S(20),
      ),
    );
    textPainter.paint(canvas, Offset(0, 0));

    // y 轴文字
    for (int i = 0; i < LINE_NUM; i++) {
      textPainter.text = TextSpan(
        text: '${(LINE_NUM - i - 1) * 1000}', // 模拟数据
        style: new TextStyle(
          color: Colors.black,
          fontSize: S(24),
        ),
      );
       // 调用此方法的目的为得到上面所输入的文字宽度与高度
      textPainter.layout();
      //  减去 textPainter.width / 2 ,textPainter.height / 2 目的为文字居中显示
      textPainter.paint(canvas,
          Offset((yTextwidth - textPainter.width) / 2, i * LINE_SPACE - textPainter.height / 2 + Y_TEXT_HEIGHT));
    }

    double xTextWidth = width - yTextwidth;
    var temp = xTextWidth / 4;
    // x 文字
    for (int i = 1; i < 5; i++) {
      textPainter.text = TextSpan(
        text: '$i 月',
        style: new TextStyle(
          color: Colors.black,
          fontSize: 12,
        ),
      );
       // 调用此方法的目的为得到上面所输入的文字宽度与高度
      textPainter.layout();
      // 减去 textPainter.width / 2 目的为文字居中显示
      textPainter.paint(
          canvas,getCurvePath(
          Offset(yTextwidth + temp * i - temp / 2 - textPainter.width / 2,
              (LINE_NUM - 1) * LINE_SPACE + 6 + Y_TEXT_HEIGHT));
    }
    
    ...
    
}

绘制曲线

获取曲线坐标

在flutter中可以通过Path绘制线条,把已知的四个点连起来,就是基本的折线统计图,但要绘制出点与点之前平滑过渡的曲线,就需要另外一个方法得到曲线的坐标,这个方法就是cos函数.

做之前先回忆一下cos长啥样.

绘制所需要的就是0 ~ 2π 间的曲线.

  /// 获取曲线路径
  /// [v1] 第一个Y轴值 [v2] 第二个Y轴值
  /// [index] X轴坐标位置
  Path getCurvePath(double v1, double v2, int index, Path path) {
    int clipNum = 30; // 一段曲线被分割绘制的数量,越大曲线越平滑.
    double temp = 1 / clipNum; // 遍历运算用到的临时数值
    bool isNegativeNumber; // 是否为负数
    double diff = (v1 - v2).abs(); // 两点之间的差值
    isNegativeNumber = (v1 - v2) < 0; // 判断正负
    for (int i = 0; i < clipNum; i++) {
      path.lineTo(
        // x点坐标值,x轴不参与cos运算
        getX(temp * i + index.toDouble()),
        // y点坐标值
        // 公式 y = cos(v) + 1 , isNegativeNumber 为true时,用到的是π~2π之间的曲线,为false时,用到的是0~π间的曲线.
        // 通过公式运算之后再与实际大小做比相乘得出实际结果,添加到Path
        getY((cos((isNegativeNumber ? pi : 0) + pi * temp * i) + 1) * diff / 2 + (isNegativeNumber ? v1 : v2)));
    }
    // 返回Path
    return path;
  }

  /// 获取Y轴坐标
  double getY(double value) => (MAX_VALUE - value) / MAX_VALUE * (LINE_NUM - 1) * LINE_SPACE + Y_TEXT_HEIGHT;

  /// 获取X轴坐标
  double getX(double index) => yTextwidth + unitWidth / 2 + index * unitWidth;

以上代码为得到曲线个点坐标的方法,接下来绘制曲线

绘制

@override
void paint(Canvas canvas, Size size) {

    ...

   List<double> values = List();
    for (int i = 0; i < 4; i++) {
      values.add(700.0 * i + 1000); // 模拟数据
    }
    Path path = Path(); // 将曲线路径点加入到'Path'路径中
    for (int i = 0; i < values.length - 1; i++) {
      double v1 = values[i];
      double v2 = values[i + 1];
      if (i == 0) {
        path.moveTo(getX(i.toDouble()), getY(v1));
      }
      path = getCurvePath(v1, v2, i, path); // 获取曲线路径
    }
    
    // 绘制路径
    canvas.drawPath(path, painter);
    path.close();
    
    ...
    
}

完成曲线统计图绘制.

最后

这就是Flutter自定义View的基本使用方法,其中还有许多细节未展示,详细内容请下载完整代码.

以上为全部内容,如有错误请指正.欢迎转载,评论.

完整代码地址
文章地址