Flutter之CustomPaint 绘制贝塞尔曲线图表(三)

49 篇文章 6 订阅
47 篇文章 0 订阅

简介

继上两篇所说,在功能实现后,补全这个虽然残缺但是比较有学习价值的DEMO:

Flutter - 仿Airbnb的价格区间筛选器。(一)

Flutter - 仿Airbnb的价格区间筛选器。(二)

Flutter-CustomPaint 绘制贝塞尔曲线图表(三)

页面布局

如之前一样,我习惯把介绍写到注释里,这样方便联系代码,不会导致阅读混乱。

主要是两部分:

1, CustomPaint(),通过个我们可以绘制图表,他有三个参数要注意,

分别是:painter , child ,foregroundPainter

绘制顺序依次是 painter , child ,foregroundPainter 后者会覆盖前者

2,RangeSlider,用于刷新 左右截取的值,以演示路径截取的效果

Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          CustomPaint(
            size: size,
            /// 绘制顺序依次是 painter  ,  child  ,foregroundPainter 后者会覆盖前者
            painter: buildPainter(),
            //foregroundPainter: ,
//            child: Container(
//              width: size.width,
//              height: size.height,
//              color: Colors.white,
//            ),
          ),
          SizedBox(
            height: 30,
          ),
          RangeSlider(
              values: RangeValues(leftValue,rightValue),
              divisions: 100,
              min: 0.0,
              max: 1.0,
              onChanged: (values){
                debugPrint("range value : $values");
                leftValue = values.start;
                rightValue = values.end;
                setState(() {

                });
              })
        ],
      )

CustomPaint是主要部分,通过传入一个painter就可以绘制任何东西,先展示一下buildPainter()的初始化内容:

ChartPainter buildPainter(){
    //基础数据
    var dataList = [
      ChartBean(x: "\$2000", y: 32),
      ChartBean(x: "\$1100", y: 48),
      ChartBean(x: "\$1400", y: 32),
      ChartBean(x: "\$500", y: 24),
      ChartBean(x: "\$800", y: 50),
      ChartBean(x: "\$1800", y: 25),
      ChartBean(x: "\$1200", y: 18),
      ChartBean(x: "\$2000", y: 32),
      ChartBean(x: "\$1100", y: 48),
      ChartBean(x: "\$1400", y: 32),
    ];
    return ChartPainter(
        //左右截取的值 默认是 0 - 1
      leftStart: leftValue,rightEnd: rightValue,
      chartBeans: dataList,
      lineColor: Colors.blueAccent,//线的颜色
      //图表填充颜色
      fillColors: [
        Colors.orange.withOpacity(0.8),
        Colors.orangeAccent.withOpacity(0.5)
      ],
      lineWith: 3,
      //y轴刻度数量
      yAxisNum: dataList.length
    );
  }

自定义Painter

ChartPainter就是我们自己的painter,它继承自CustomPainter,需要实现两个方法:

1,shouldRepaint(ChartPainter oldDelegate)是否重绘,

我们可以根据参数取到自己的变量值,然后根据需要来判断是否重绘(如,加个flag、左右截取值得变化或者动画的进度等等)

2,paint(Canvas canvas, Size size) 这个大家很熟悉了,画布和父widget给的大小,也是主要实现区域

  @override
  void paint(Canvas canvas, Size size) {
    _init(size); //初始化
    _drawLine(canvas, size); //绘制曲线
  }

生成Path

  void _init(Size size) {
    //这里增加一些边界,确保图表不是骑着屏幕边缘绘制的,美观
    //同时 会存储dataList的最小和最大值 minMax (List<double>)
    initBorder(size); 
    initPath(size);//初始化路径
  }

initPath()方法就是用来计算和生成曲线的路径了:

注意这里的start X,Y 和 end X,Y 分别是左下和右上。

void initPath(Size size) {
    if (path == null) {
      if (chartBeans != null && chartBeans.length > 0 && minMax[0] > 0) {
        path = Path();
        double preX,//前一个数据的 x 值
            preY,//前一个数据的 y 值
            currentX,
            currentY;
        int length =  chartBeans.length;
        //x轴 两值之间的间距
        double W = _fixedWidth / (length - 1);

        for (int i = 0; i < length; i++) {
          if (i == 0) {
            var key = startX;//第一个数的x值
            // chartBeans[i].y / maxMin[0] 算出当前y值为最大的值得百分比 * 表高 得出具体所对应的Y轴值
            //用 startY - y值  可以得到最终在屏幕上的y值
            var value = (startY - chartBeans[i].y / minMax[0] * _fixedHeight);
            //移动到对应数据的位置
            path.moveTo(key, value);
            continue;
          }
          //绘制完第一个点后,向右平移一个 宽度(w)
          currentX = startX + W * i;
          //前一个数据的 x 值
          preX = startX + W * (i - 1);
          //前一个数据的y值
          preY = (startY - chartBeans[i - 1].y / minMax[0] * _fixedHeight);
          currentY = (startY - chartBeans[i].y / minMax[0] * _fixedHeight);

          //绘制贝塞尔路径  (可以本站搜索或者百度,有很详尽的介绍)
          path.cubicTo((preX + currentX) / 2, preY, // control point 1
              (preX + currentX) / 2, currentY,      //  control point 2
              currentX, currentY);
          //如果使用直线 可以直接.lineTo(...)
        }
      }
    }
  }

支持根据dataList的数据生成的曲线path就完成了,下面开始绘制

绘制Path

通过_drawLine(canvas, size)方法根据刚才生成的path进行绘制,具体如下:

_drawLine(Canvas canvas, Size size) {
    if (chartBeans == null || chartBeans.length == 0) return;
    var paint = Paint()
      ..isAntiAlias = true//抗锯齿
      ..strokeWidth = lineWith
      ..strokeCap = StrokeCap.round//这个是画笔结束后的样式:这里是圆形
      ..color = lineColor
      ..style = PaintingStyle.stroke;
    ///安卓原生端 可以根据PathMeasure 得到路径每个点的坐标
    ///flutter 是通过computeMetrics ,作用基本是一致的.
    if (minMax[0] <= 0) return;
    var pathMetrics = path.computeMetrics(forceClosed: false);//第二个参数是否要连接起点
    ///生成的list,每个元素代表一条path生成的Metrics,(咱们这里只有一个path,所以元素只有一个)
    var list = pathMetrics.toList();
    ///这里我们根据 left right 对path 进行截取
    var length = list.length.toInt() -
        (list.length.toInt() * leftStart) - (list.length.toInt() * (1-rightEnd));
    Path linePath = new Path();
    //填充颜色区域
    Path shadowPath = new Path();
    for (int i = 0; i < length; i++) {
      //开始抽取位置
      double startExtr = list[i].length * (leftStart );
      //结束抽取位置
      double endExtr = list[i].length * (rightEnd);
      var extractPath =
      list[i].extractPath(startExtr, endExtr , startWithMoveTo: true);
      //extractPath.getBounds()
      linePath.addPath(extractPath, Offset(0, 0));
      shadowPath = extractPath;
    }

    ///给画笔添加着色器,可以通过各种渐变createShader()方法生成着色器
    /// 这里使用的是线性渐变、还有RadialGradient,SweepGradient
    if (fillColors != null) {
      var shader = LinearGradient(
          begin: Alignment.topCenter,
          end: Alignment.bottomCenter,
          tileMode: TileMode.clamp,
          colors: fillColors)
          .createShader(Rect.fromLTRB(startX, endY, startX, startY));

      ///从path的最后一个点连接起始点,形成一个闭环
      shadowPath
        ..lineTo(startX + (_fixedWidth * rightEnd) , startY)
        ..lineTo((startX+ (endX * leftStart ) ), startY)
        ..close();
      ///先画阴影
      canvas
        ..drawPath(
            shadowPath,
            new Paint()
              ..shader = shader
              ..isAntiAlias = true
              ..style = PaintingStyle.fill);
    }
    //再画曲线,目的是防止阴影覆盖曲线
    canvas.drawPath(linePath, paint);
  }

结束&DEMO

至此,整个图表就绘制完成了,我们将变量left和right传给painter,然后通过range slider来调节这两个变量并刷新,已达到截取路径的效果,具体可以运行DEMO。

https://github.com/bladeofgod/chart_price_slider

PS:我们还可以传入点击position,并根据这个做更多的交互功能在上面,这个就待后面再实现吧。

:) 谢谢大家

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值