Flutter CustomPainter实现半圆形饼图

效果图

  • CustomPaint
    CustomPaint 是一个继承自 SingleChildRenderObjectWidget 的控件,因此我们不能用 setState 的方式来刷新它,但是我们可以通过传值不断刷新build方法取重新绘制它.它需要传入painter结合size属性进行绘制.
    return CustomPaint(
      size: Size(double.infinity, w(130)),
      painter: CurvePainter(),
    );

CurvePainter继承了CustomPainter,CustomPainter需要实现两个方法:

class CurvePainter extends CustomPainter {

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

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}

paint() 中进行绘制,可以在这获得画布 Canvas 和 画布的大小 Size。
shouldRepaint() 返回 true 才会进行重绘,否则就只会绘制一次。你可以通过一些条件判断来决定是否每次绘制,这样能够节约系统资源。

然后我们开始构思如何如何实现其效果,先抛开动画效果:

  1. 先绘制一个半圆(考虑半圆不是闭合的,半圆的边线宽度是画笔的strokeWidth)
  2. 在绘制半圆内的线条半圆

发现结束了,这么简单吗?需要考虑的重点其实是:半圆是百分比组合成的,每一块儿都只是一个圆弧.看一下画圆弧的方法:

drawArc(Rect rect, double startAngle, double sweepAngle, bool useCenter, Paint paint)

rect 参数是一个矩形,先跳过,待会说.
startAngle 参数是起始角度.
sweepAngle 参数是扫过的角度.
useCenter 参数是圆弧是否闭合.
paint 是画笔.

注意:角度是通过π来计算的,2π = 360度.起始角度是从三点钟方向顺时针开始.但是我们需要从九点钟方向顺时针开始,也就是我们需要逆时针180度 = -π = -pi(flutter用pi表示π)

那么我们可以这样计算:第一段p1是-pi到百分比弧度,第二段p2开始弧度是p1的结束弧度,扫过的角度就是百分比弧度,依次类推.

Rect rect = Rect.fromCircle(
        center: Offset(size.width / 2, size.height), radius: radius);

    // 绘制p1
    double runningDegree = pi * runningRate;
    canvas.drawArc(rect, -pi, runningDegree, false, curvePaint);

    // 绘制p2
    curvePaint.color = waitingColor;
    double waitingDegree = pi * waitingRate;
    canvas.drawArc(
        rect, -pi * (1 - runningRate), waitingDegree, false, curvePaint);

    // 绘制p3
    curvePaint.color = maintainingColor;
    double maintainingDegree = pi * maintainingRate;
    canvas.drawArc(rect, -pi * (1 - runningRate - waitingRate),
        maintainingDegree, false, curvePaint);

    // 绘制p4
    curvePaint.color = breakDownColor;
    double breakDownDegree = pi * breakDownRate;
    canvas.drawArc(
        rect,
        -pi * (1 - runningRate - waitingRate - maintainingRate),
        breakDownDegree,
        false,
        curvePaint);

画内部圆弧要知道:它的圆心和画弧度的圆心是一样的.都是x:宽度的一半,y:高度.

    /// 画内部圆弧
    curvePaint.color = runningColor;
    curvePaint.strokeWidth = 1;
    Rect insideR = Rect.fromCircle(
        center: Offset(size.width / 2, size.height), radius: radius - 30);
    canvas.drawArc(insideR, -pi, pi, false, curvePaint);

接下来就是绘制中心文字:

    /// 画文字
    TextPainter centerTitlePainter = TextPainter(
        textAlign: TextAlign.center,
        text: TextSpan(
            text: centerTitle.toString(),
            style:
                TextStyle(color: centerTitleColor, fontSize: centerTitleSize)),
        textDirection: TextDirection.ltr);
    centerTitlePainter.layout(minWidth: 40);
    centerTitlePainter.paint(
        canvas,
        Offset((size.width - centerTitlePainter.width) / 2,
            size.height - centerTitlePainter.height - 10));

到这里绘制就结束了,那么如何实现动画效果呢?
**动画效果:**每一段的p1-p4的弧度*当前动画已经执行的比率就是当前动画时刻4段弧度数,直至比率等于1

	// 绘制p1
    runningRate = runningRate * animValue;
    double runningDegree = pi * runningRate;
    canvas.drawArc(rect, -pi, runningDegree, false, curvePaint);

animValue就是当前动画的比率.(如果对动画不了解的可以去看看Flutter官网加深印象)

资源下载移步:
https://download.csdn.net/download/qq1377399077/12980300

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个简单的空心Flutter 代码示例: ``` import 'package:flutter/material.dart'; import 'dart:math'; class DonutChart extends StatelessWidget { final List<Data> data; DonutChart({required this.data}); @override Widget build(BuildContext context) { return CustomPaint( size: Size(MediaQuery.of(context).size.width, 250), painter: DonutChartPainter(data), ); } } class DonutChartPainter extends CustomPainter { final List<Data> data; final double _total = 100; double _start = -90.0; DonutChartPainter(this.data); @override void paint(Canvas canvas, Size size) { var paint = Paint() ..style = PaintingStyle.stroke ..strokeWidth = 20.0; final radius = min(size.width / 2, size.height / 2) - (paint.strokeWidth / 2); double total = data.fold(0, (prev, element) => prev + element.value); for (var slice in data) { paint.color = slice.color; double sweepAngle = (slice.value / total) * 360; canvas.drawArc( Rect.fromCircle(center: Offset(size.width / 2, size.height / 2), radius: radius), radians(_start), radians(sweepAngle), false, paint); _start += sweepAngle; } } @override bool shouldRepaint(DonutChartPainter old) { return old.data != data; } double radians(double degree) { return degree * pi / 180; } } class Data { final String label; final double value; final Color color; Data({required this.label, required this.value, required this.color}); } ``` 需要注意的是,在使用时需要提供一个包含 `Data` 对象的数组,其中每个 `Data` 对象代表一个扇形,包含一个标签、一个值和一个颜色。然后将这个数组传递给 `DonutChart` 组件即可。 另外需要注意的是,这个代码示例只是一个简单的实现,还有很多可以改进的地方,比如添加动画、增加标签等。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值