flutteranimationflutter-dependenciesflutter-animation

how to make custom circular progress indicator in flutter


[![enter image description here][1]][1]

[1]: https://i.sstatic.net/bZSXtA7U.png**strong text**

import 'package:flutter/material.dart';
import 'dart:math';

void main() {
  runApp(const MaterialApp(
    debugShowCheckedModeBanner: false,
    home: Scaffold(
      backgroundColor: Colors.white,
      body: Center(child: RotatingCircularProgress()),
    ),
  ));
}

class RotatingCircularProgress extends StatefulWidget {
  const RotatingCircularProgress({super.key});

  @override
  State<RotatingCircularProgress> createState() => _RotatingCircularProgressState();
}

class _RotatingCircularProgressState extends State<RotatingCircularProgress>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 2),
    )..repeat(); // Infinite rotation
  }

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: 100,
      height: 100,
      child: AnimatedBuilder(
        animation: _controller,
        builder: (context, child) {
          return Transform.rotate(
            angle: _controller.value * 2 * pi, // Rotate whole indicator
            child: CustomPaint(
              painter: CircularProgressPainter(),
            ),
          );
        },
      ),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

class CircularProgressPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final center = size.center(Offset.zero);
    final radius = size.width / 2;
    final strokeWidth = 10.0;
    const startAngle = pi;//-pi / 2; // Start from top
    const sweepAngle = pi*1.7;//1.8 * pi; // ¾ circle

    // Gradient from green (start) to white (end)
    final gradient = SweepGradient(
      colors: [
        Colors.green,                // Start Green
        Colors.green.withOpacity(0.7),
        Colors.green.withOpacity(0.4),
        Colors.green.withOpacity(0.1),
        Colors.white,                // End White
      ],
      stops: const [0.0, 0.3, 0.6, 0.85, 1.0],  // Smooth transition
      tileMode: TileMode.clamp,  // Avoids unwanted repetition
    );

    final paint = Paint()
      ..shader = gradient.createShader(Rect.fromCircle(center: center, radius: radius))
      ..style = PaintingStyle.stroke
      ..strokeWidth = strokeWidth
      ..strokeCap = StrokeCap.round; // Rounded at the start

    // Draw arc
    canvas.drawArc(
      Rect.fromCircle(center: center, radius: radius),
      startAngle,
      sweepAngle,
      false,
      paint,
    );
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

how to make custom circular progress indicator as in the pic. I've tried multiple ways but i am unable to achieve something like the one in the picture.I am trying to build exactly as in the picture. I'm new to animations. Help or guidance is highly appreciated.


Solution

  • I just adjusted startAngle and sweepAngle, might achieve what you want

    import 'package:flutter/material.dart';
    import 'dart:math';
    
    void main() {
      runApp(const MaterialApp(
        debugShowCheckedModeBanner: false,
        home: Scaffold(
          backgroundColor: Colors.white,
          body: Center(child: RotatingCircularProgress()),
        ),
      ));
    }
    
    class RotatingCircularProgress extends StatefulWidget {
      const RotatingCircularProgress({super.key});
    
      @override
      State<RotatingCircularProgress> createState() => _RotatingCircularProgressState();
    }
    
    class _RotatingCircularProgressState extends State<RotatingCircularProgress>
        with SingleTickerProviderStateMixin {
      late AnimationController _controller;
    
      @override
      void initState() {
        super.initState();
        _controller = AnimationController(
          vsync: this,
          duration: const Duration(seconds: 2),
        )..repeat(); // Infinite rotation
      }
    
      @override
      Widget build(BuildContext context) {
        return SizedBox(
          width: 100,
          height: 100,
          child: AnimatedBuilder(
            animation: _controller,
            builder: (context, child) {
              return Transform.rotate(
                angle: _controller.value * 2 * pi, // Rotate whole indicator
                child: CustomPaint(
                  painter: CircularProgressPainter(),
                ),
              );
            },
          ),
        );
      }
    
      @override
      void dispose() {
        _controller.dispose();
        super.dispose();
      }
    }
    
    class CircularProgressPainter extends CustomPainter {
      @override
      void paint(Canvas canvas, Size size) {
        final center = size.center(Offset.zero);
        final radius = size.width / 2;
        final strokeWidth = 10.0;
        const startAngle = 0.2;//-pi / 2; // Start from top
        const sweepAngle = pi*1.7 + 0.2;//1.8 * pi; // ¾ circle
    
        // Gradient from green (start) to white (end)
        final gradient = SweepGradient(
          startAngle: startAngle,
          endAngle: sweepAngle,
          colors: [
            Colors.white,                // End White
            Colors.green,                // Start Green
          ],
          stops: const [0.0, 1.0],  // Smooth transition
          tileMode: TileMode.clamp,  // Avoids unwanted repetition
        );
    
        final paint = Paint()
          ..shader = gradient.createShader(Rect.fromCircle(center: center, radius: radius))
          ..style = PaintingStyle.stroke
          ..strokeWidth = strokeWidth
          ..strokeCap = StrokeCap.round; // Rounded at the start
    
        // Draw arc
        canvas.drawArc(
          Rect.fromCircle(center: center, radius: radius),
          startAngle,
          sweepAngle,
          false,
          paint,
        );
      }
    
      @override
      bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
    }