Last active
December 18, 2025 06:36
-
-
Save Shubham-Narkhede/24962c94b0fb52eea162b555b1f4d67c to your computer and use it in GitHub Desktop.
Camera for web
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import 'dart:math'; | |
| import 'package:flutter/material.dart'; | |
| class DonutProgress extends StatefulWidget { | |
| final double percent; // 0 - 100 | |
| const DonutProgress({super.key, required this.percent}); | |
| @override | |
| State<DonutProgress> createState() => _DonutProgressState(); | |
| } | |
| class _DonutProgressState extends State<DonutProgress> | |
| with SingleTickerProviderStateMixin { | |
| late AnimationController _controller; | |
| bool isHovered = false; | |
| @override | |
| void initState() { | |
| super.initState(); | |
| _controller = AnimationController( | |
| vsync: this, | |
| duration: const Duration(milliseconds: 900), | |
| )..forward(); | |
| } | |
| @override | |
| void dispose() { | |
| _controller.dispose(); | |
| super.dispose(); | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| return MouseRegion( | |
| onEnter: (_) => setState(() => isHovered = true), | |
| onExit: (_) => setState(() => isHovered = false), | |
| child: AnimatedBuilder( | |
| animation: _controller, | |
| builder: (_, __) { | |
| return CustomPaint( | |
| size: const Size(200, 200), | |
| painter: DonutPainter( | |
| percent: widget.percent, | |
| animationValue: _controller.value, | |
| isHovered: isHovered, | |
| ), | |
| ); | |
| }, | |
| ), | |
| ); | |
| } | |
| } | |
| class DonutPainter extends CustomPainter { | |
| final double percent; | |
| final double animationValue; | |
| final bool isHovered; | |
| DonutPainter({ | |
| required this.percent, | |
| required this.animationValue, | |
| required this.isHovered, | |
| }); | |
| @override | |
| void paint(Canvas canvas, Size size) { | |
| final center = Offset(size.width / 2, size.height / 2); | |
| final radius = size.width / 2 - 16; | |
| // Background ring | |
| final bgPaint = Paint() | |
| ..color = Colors.white.withOpacity(0.15) | |
| ..style = PaintingStyle.stroke | |
| ..strokeWidth = 18 | |
| ..strokeCap = StrokeCap.round; | |
| canvas.drawCircle(center, radius, bgPaint); | |
| // Foreground arc | |
| final fgPaint = Paint() | |
| ..color = const Color(0xFF35D6E7) | |
| ..style = PaintingStyle.stroke | |
| ..strokeWidth = isHovered ? 22 : 18 | |
| ..strokeCap = StrokeCap.round; | |
| final sweepAngle = | |
| (percent / 100) * 2 * pi * animationValue; | |
| canvas.drawArc( | |
| Rect.fromCircle(center: center, radius: radius), | |
| -pi / 2, | |
| sweepAngle, | |
| false, | |
| fgPaint, | |
| ); | |
| // Center text | |
| _drawCenterText(canvas, size); | |
| } | |
| void _drawCenterText(Canvas canvas, Size size) { | |
| final textPainter = TextPainter( | |
| text: TextSpan( | |
| text: "${percent.toInt()}%", | |
| style: const TextStyle( | |
| fontSize: 28, | |
| fontWeight: FontWeight.bold, | |
| color: Colors.white, | |
| ), | |
| ), | |
| textAlign: TextAlign.center, | |
| textDirection: TextDirection.ltr, | |
| )..layout(); | |
| final offset = Offset( | |
| size.width / 2 - textPainter.width / 2, | |
| size.height / 2 - textPainter.height / 2, | |
| ); | |
| textPainter.paint(canvas, offset); | |
| } | |
| @override | |
| bool shouldRepaint(covariant DonutPainter oldDelegate) => | |
| animationValue != oldDelegate.animationValue || | |
| isHovered != oldDelegate.isHovered; | |
| } | |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://youtube.com/shorts/OlfTH7Qahl0?si=T7KYkvcx2O_jljFC