Flutter-圆形背景&渐变进度条

320 阅读2分钟

这篇文章主要是通过Canvas实现一个圆形渐变进度条。主要功能:

  1. 支持多种背景渐变色
  2. 任意弧度,进度条可以不是圆形
  3. 可以自定义粗细、两端是否圆角等样式

示例:

class SSLGradientCircularProgressIndicator extends StatelessWidget{
  //半径
  final double radius;
  //两端是否圆角
  final bool strokeCapRound;
  //当前进度
  final double value;
  //粗细
  final double strokeWidth;
  //进度条背景色
  final Color backgroundColor;
  //进度条总弧度,2*PI为整圆,小于2*PI为圆弧
  final double totalAngle;
  //渐变色数组
  final List<Color> colors;
  //渐变色的终止点,对应colors属性
  final List<double>? stops;

  const SSLGradientCircularProgressIndicator({
    Key? key,
    this.strokeWidth = 2.0,
    required this.radius,
    required this.colors,
    this.stops,
    this.strokeCapRound = false,
    this.backgroundColor = Colors.white,
    this.totalAngle = 2*pi,
    this.value = 0,
  }):super(key: key);


  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    double offset = 0.0;
    if (strokeCapRound){
      offset = asin(strokeWidth/(radius*2 - strokeWidth));
    }
    var colorsTem = colors;
    // if (colorsTem == null){
    //   Color color = Theme.of(context).colorScheme.secondary;
    //   colorsTem = [color, color];
    // }
    return Transform.rotate(
        angle: -pi/2.0 - offset,
        child: CustomPaint(
            size: Size.fromRadius(radius),
            painter: SSLGradientCircularProgressPainter(
              strokeWidth: strokeWidth,
              strokeCapRound:  strokeCapRound,
              backgroundColor: backgroundColor,
              value: value,
              total: totalAngle,
              radius: radius,
              colors: colors,
              stops: stops,
            ),
        ),
    );
  }
}

class SSLGradientCircularProgressPainter extends CustomPainter{
  final double strokeWidth;
  final bool strokeCapRound;
  final double value;
  final Color backgroundColor;
  final List<Color> colors;
  final double total;
  final double? radius;
  final List<double>? stops;
  SSLGradientCircularProgressPainter({
    this.strokeWidth = 10.0,
    this.strokeCapRound = true,
    this.backgroundColor = Colors.white,
    this.radius,
    this.total = 2*pi,
    this.stops,
    this.value = 0,
    required this.colors,
  });

  @override
  void paint(Canvas canvas, Size size) {
    if (radius != null){
      size = Size.fromRadius(radius!);
    }
    double offset = strokeWidth/2.0;
    double start = 0.0;

    if (strokeCapRound){
      start = asin(strokeWidth / (size.width - strokeWidth));
    }

    Rect rect = Offset(offset, offset) & Size(size.width - strokeWidth, size.height - strokeWidth);

    var paint = Paint()
    ..strokeCap = strokeCapRound ? StrokeCap.round : StrokeCap.butt
    ..style = PaintingStyle.stroke
    ..isAntiAlias = true
    ..strokeWidth = strokeWidth;

    //画背景色
    if (backgroundColor != Colors.transparent){
      paint.color = backgroundColor;
      canvas.drawArc(rect, start, total, false, paint);
    }
    //画前景,应用渐变
    if (value > 0){
      paint.shader = SweepGradient(
        colors: colors,
        startAngle: 0.0,
        endAngle: value,
        stops: stops,
      ).createShader(rect);

      canvas.drawArc(rect, start, value, false, paint);
    }

  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    // TODO: implement shouldRepaint
    return true;
  }
}

class SSLGradientCircularProgressRoute extends StatefulWidget{
  const SSLGradientCircularProgressRoute({Key? key}):super(key: key);

  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return SSLGradientCircularProgressRouteState();
  }
}

class SSLGradientCircularProgressRouteState extends State<SSLGradientCircularProgressRoute> with TickerProviderStateMixin{
  late AnimationController animationController;
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    animationController = AnimationController(vsync: this,duration: const Duration(seconds: 3));
    bool isForward = true;
    animationController.addStatusListener((status) {
      if (status == AnimationStatus.completed || status == AnimationStatus.dismissed){
        if (isForward){
          animationController.reverse();
        }else{
          animationController.forward();
        }
      }else{
        isForward = false;
      }
    });
    animationController.forward();
  }

  @override
  void dispose() {
    // TODO: implement dispose
    animationController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(
        title: const Text("SSL GradientCircular"),
      ),
      body: SingleChildScrollView(
        child: Center(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              AnimatedBuilder(animation: animationController, builder: (BuildContext context, child){
                return Padding(
                  padding: const EdgeInsets.symmetric(vertical: 16.0),
                  child: Column(
                    children: [
                      Wrap(
                        spacing: 10.0,
                        runSpacing: 16.0,
                        children: [
                          SSLGradientCircularProgressIndicator(
                            radius: 50.0,
                            colors: const [Colors.blue, Colors.red],
                            strokeWidth: 3.0,
                            value: animationController.value,
                          ),
                          SSLGradientCircularProgressIndicator(
                            radius: 50.0,
                            colors: const [Colors.yellow, Colors.orange],
                            strokeWidth: 3,
                            value: animationController.value,
                          ),
                          SSLGradientCircularProgressIndicator(
                            radius: 50.0,
                            colors: const [Colors.purple, Colors.deepPurpleAccent],
                            strokeWidth: 3,
                            value: CurvedAnimation(
                                parent: animationController,
                                curve: Curves.decelerate
                            ).value,
                          ),
                          SSLTurnBox(
                            turns: 1 / 8,
                            child: SSLGradientCircularProgressIndicator(
                              radius: 50.0,
                              colors: const [Colors.blue, Colors.red],
                              strokeWidth: 3,
                              value: CurvedAnimation(
                                  parent: animationController,
                                  curve: Curves.ease
                              ).value,
                            ),
                          ),
                          RotatedBox(
                            quarterTurns: 1,
                            child: SSLGradientCircularProgressIndicator(
                              radius: 50.0,
                              colors: [Colors.blue.shade700, Colors.red.shade100],
                              strokeWidth: 3,
                              backgroundColor: Colors.transparent,
                              value: animationController.value,
                            ),
                          ),
                          ClipRect(
                            child: Align(
                              alignment: Alignment.topCenter,
                              heightFactor: 0.5,
                              child: Padding(
                                padding: const EdgeInsets.only(bottom: 8.0),
                                child: SizedBox(
                                  child: SSLTurnBox(
                                    turns: 0.75,
                                    child: SSLGradientCircularProgressIndicator(
                                      radius: 100.0,
                                      colors: const [Colors.teal, Colors.cyan],
                                      strokeWidth: 8,
                                      totalAngle: pi,
                                      value: animationController.value,
                                    ),
                                  ),
                                ),
                              ),
                            ),
                          ),
                          SizedBox(
                            height: 104,
                            width: 200,
                            child: Stack(
                              alignment: Alignment.center,
                              children: [
                                Positioned(
                                  height: 200,
                                  top: .0,
                                  child: SSLTurnBox(
                                    turns: 0.75,
                                    child: SSLGradientCircularProgressIndicator(
                                      radius: 50.0,
                                      colors: const [Colors.blue, Colors.red],
                                      strokeWidth: 3,
                                      value: animationController.value,
                                    ),
                                  ),
                                ),
                                Padding(
                                  padding: const EdgeInsets.only(top: 10.0),
                                  child: Text(
                                    "${(animationController.value*100).toInt()}",
                                    style: const TextStyle(
                                        fontSize: 25.0,
                                        color: Colors.blueGrey
                                    ),
                                  ),

                                ),
                              ],
                            ),
                          ),
                        ],
                      )
                    ],
                  ),
                );
              })
            ],
          ),
        ),
      ),
    );
  }
}