Flutter 空心圆环的实现方法

85 min read

Flutter中实现空心圆环有多种方法,以下列举两种常用的实现方法:

  1. 使用CustomPaint自定义绘制
class CircleProgressBar extends StatelessWidget {
  final double strokeWidth; // 圆环宽度
  final double value; // 进度值
  final Color backgroundColor; // 圆环背景色
  final Color foregroundColor; // 进度圆环颜色

  CircleProgressBar({
    this.strokeWidth = 10,
    this.value = 0,
    this.backgroundColor = Colors.grey,
    this.foregroundColor = Colors.blue,
  })  : assert(strokeWidth != null),
        assert(value != null && value >= 0 && value <= 1),
        assert(backgroundColor != null),
        assert(foregroundColor != null);

  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      painter: CircleProgressBarPainter(
        strokeWidth: strokeWidth,
        value: value,
        backgroundColor: backgroundColor,
        foregroundColor: foregroundColor,
      ),
    );
  }
}

class CircleProgressBarPainter extends CustomPainter {
  final double strokeWidth;
  final double value;
  final Color backgroundColor;
  final Color foregroundColor;

  CircleProgressBarPainter({
    this.strokeWidth = 10,
    this.value = 0,
    this.backgroundColor = Colors.grey,
    this.foregroundColor = Colors.blue,
  });

  @override
  void paint(Canvas canvas, Size size) {
    final Paint bgPaint = Paint()
      ..color = backgroundColor
      ..strokeWidth = strokeWidth
      ..style = PaintingStyle.stroke;

    final Paint fgPaint = Paint()
      ..color = foregroundColor
      ..strokeWidth = strokeWidth
      ..style = PaintingStyle.stroke
      ..strokeCap = StrokeCap.round;

    final center = Offset(size.width / 2, size.height / 2);
    final radius = (size.width - strokeWidth) / 2;

    canvas.drawCircle(center, radius, bgPaint);

    // value为0时不绘制进度圆环
    if (value > 0) {
      canvas.drawArc(
        Rect.fromCircle(center: center, radius: radius),
        -math.pi / 2,
        math.pi * 2 * value,
        false,
        fgPaint,
      );
    }
  }

  @override
  bool shouldRepaint(CircleProgressBarPainter oldDelegate) =>
      value != oldDelegate.value ||
      backgroundColor != oldDelegate.backgroundColor ||
      foregroundColor != oldDelegate.foregroundColor;
}
  1. 使用插件flutter_svg实现
import 'package:flutter_svg/flutter_svg.dart';

class CircleProgressBar extends StatelessWidget {
  final double strokeWidth; // 圆环宽度
  final double value; // 进度值
  final Color backgroundColor; // 圆环背景色
  final Color foregroundColor; // 进度圆环颜色

  CircleProgressBar({
    this.strokeWidth = 10,
    this.value = 0,
    this.backgroundColor = Colors.grey,
    this.foregroundColor = Colors.blue,
  })  : assert(strokeWidth != null),
        assert(value != null && value >= 0 && value <= 1),
        assert(backgroundColor != null),
        assert(foregroundColor != null);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: SvgPicture.asset(
        'assets/circle_progress_bar.svg',
        color: foregroundColor,
        semanticsLabel: 'CircleProgressBar',
        fit: BoxFit.cover,
        width: double.infinity,
        height: double.infinity,
        matchTextDirection: true,
        // 设置操作只为旋转
        allowDrawingOutsideViewBox: false,
        alignment: Alignment.center,
        // transform为SVG文件中的transform
        // 根据value计算出需要旋转的角度
        // 需要在SVG文件中加入transform='rotate(-90)',保证起始位置为12点钟方向
        // 如下所示:
        // <g transform='rotate(-90)'>
        //   <circle cx='50%' cy='50%' r='50%' />
        //   <path id='arc' d='M50 0 A50 50 0 ...' />
        // </g>
        // 其中-transform.value为不包含value之前的角度
        transform: Matrix4.rotationZ(-math.pi / 2 - math.pi * 2 * value),
      ),
    );
  }
}