115 lines
2.9 KiB
Dart
115 lines
2.9 KiB
Dart
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<AppLoadingRing> createState() => _AppLoadingRingState();
|
|
}
|
|
|
|
class _AppLoadingRingState extends State<AppLoadingRing>
|
|
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<double>(
|
|
tween: Tween<double>(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;
|
|
}
|