Skip to content

Instantly share code, notes, and snippets.

@Shubham-Narkhede
Last active December 18, 2025 06:36
Show Gist options
  • Select an option

  • Save Shubham-Narkhede/24962c94b0fb52eea162b555b1f4d67c to your computer and use it in GitHub Desktop.

Select an option

Save Shubham-Narkhede/24962c94b0fb52eea162b555b1f4d67c to your computer and use it in GitHub Desktop.
Camera for web
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;
}
@Shubham-Narkhede
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment