import 'dart:math' as math; import 'package:flutter/material.dart'; class AppLoadingRing extends StatefulWidget { const AppLoadingRing({super.key, this.size = 44, this.color}); final double size; final Color? color; @override State createState() => _AppLoadingRingState(); } class _AppLoadingRingState extends State with SingleTickerProviderStateMixin { late final AnimationController _rotationController; @override void initState() { super.initState(); _rotationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 1100), )..repeat(); } @override void dispose() { _rotationController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final ringColor = widget.color ?? IconTheme.of(context).color ?? Theme.of(context).colorScheme.onSurface; return TweenAnimationBuilder( tween: Tween(begin: 0, end: 1), duration: const Duration(milliseconds: 200), curve: Curves.easeOut, builder: (context, fadeInOpacity, child) => Opacity(opacity: fadeInOpacity, child: child), child: RepaintBoundary( child: SizedBox( width: widget.size, height: widget.size, child: AnimatedBuilder( animation: _rotationController, builder: (context, _) => CustomPaint( painter: _LoadingRingPainter( rotationTurns: _rotationController.value, color: ringColor, ), ), ), ), ), ); } } class _LoadingRingPainter extends CustomPainter { _LoadingRingPainter({required this.rotationTurns, required this.color}); final double rotationTurns; final Color color; static const double strokeWidth = 3; static const double _sweepAngle = math.pi * 5 / 3; @override void paint(Canvas canvas, Size size) { final center = Offset(size.width / 2, size.height / 2); final radius = (math.min(size.width, size.height) - strokeWidth) / 2; final arcRect = Rect.fromCircle(center: center, radius: radius); final rotationAngle = rotationTurns * 2 * math.pi; final gradient = SweepGradient( startAngle: 0, endAngle: 2 * math.pi, transform: GradientRotation(rotationAngle), colors: [ color.withValues(alpha: 0.0), color.withValues(alpha: 0.25), color, ], stops: const [0.0, 0.5, _sweepAngle / (2 * math.pi)], ); final arcPaint = Paint() ..style = PaintingStyle.stroke ..strokeWidth = strokeWidth ..strokeCap = StrokeCap.round ..shader = gradient.createShader(arcRect); canvas.drawArc(arcRect, rotationAngle, _sweepAngle, false, arcPaint); } @override bool shouldRepaint(_LoadingRingPainter oldDelegate) => oldDelegate.rotationTurns != rotationTurns || oldDelegate.color != color; }