import 'package:flutter/material.dart'; /// ==================================================================== /// 1. KRSimpleLoading: 旋转和扇形加载动画 (支持暂停,已修复重合) /// ==================================================================== class KRSimpleLoading extends StatefulWidget { final Color? color; final double size; final Duration duration; final bool isPaused; // 控制动画暂停/恢复 const KRSimpleLoading({ super.key, this.color, this.size = 30.0, // 环的宽高默认为 30.0 像素 this.duration = const Duration(milliseconds: 1000), this.isPaused = false, // 默认不暂停 }); @override State createState() => _KRSimpleLoadingState(); } class _KRSimpleLoadingState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation _rotationAnimation; // 环的宽度 5px static const double _strokeWidth = 5.0; @override void initState() { super.initState(); _controller = AnimationController( duration: widget.duration, vsync: this, ); _rotationAnimation = Tween( begin: 0.0, end: 1.0, ).animate(CurvedAnimation( parent: _controller, curve: Curves.linear, )); // 根据初始状态决定是重复播放还是停止 _setAnimationState(widget.isPaused); } void _setAnimationState(bool isPaused) { if (isPaused) { _controller.stop(); } else { // 如果之前停止了,调用 repeat 重新开始 _controller.repeat(); } } @override void didUpdateWidget(covariant KRSimpleLoading oldWidget) { super.didUpdateWidget(oldWidget); // 监听 isPaused 参数的变化,并更新动画状态 if (widget.isPaused != oldWidget.isPaused) { _setAnimationState(widget.isPaused); } } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final defaultColor = widget.color ?? Theme.of(context).primaryColor; // 强制动画组件拥有固定的 30x30 尺寸 return SizedBox( width: widget.size, height: widget.size, child: AnimatedBuilder( animation: _controller, builder: (context, child) { return Transform.rotate( angle: _rotationAnimation.value * 2 * 3.14159, // 关键修复: 使用 Stack 和 Center+SizedBox 确保两个环完美重合 child: Stack( children: [ // 1. 底环 (Container): 绘制 30x30 边界内的 5px 边框 Positioned.fill( child: Container( decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( color: defaultColor.withOpacity(0.3), width: _strokeWidth, ), ), ), ), // 2. 顶扇形 (CircularProgressIndicator): // 通过 Center+SizedBox 强制其绘制区域收缩,以消除渲染偏差。 Center( child: SizedBox( // 关键:将尺寸减去笔触宽度 (30 - 5 = 25) width: widget.size - _strokeWidth, height: widget.size - _strokeWidth, child: CircularProgressIndicator( value: 0.2, // 彩色扇形长度 strokeCap: StrokeCap.round, // 圆角 strokeWidth: _strokeWidth, // 宽度 5px backgroundColor: Colors.transparent, // 必须透明 valueColor: AlwaysStoppedAnimation( defaultColor, ), ), ), ), ], ), ); }, ), ); } } class KRSimplePulse extends StatefulWidget { final Color? color; final double size; final Duration duration; const KRSimplePulse({ super.key, this.color, this.size = 40.0, this.duration = const Duration(milliseconds: 1500), }); @override State createState() => _KRSimplePulseState(); } class _KRSimplePulseState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation _animation; @override void initState() { super.initState(); _controller = AnimationController( duration: widget.duration, vsync: this, ); _animation = Tween( begin: 0.0, end: 1.0, ).animate(CurvedAnimation( parent: _controller, curve: Curves.easeInOut, )); _controller.repeat(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: _animation, builder: (context, child) { return Transform.scale( scale: 0.5 + (_animation.value * 0.5), child: Opacity( opacity: 1.0 - _animation.value, child: Container( width: widget.size, height: widget.size, decoration: BoxDecoration( shape: BoxShape.circle, color: widget.color ?? Theme.of(context).primaryColor, ), ), ), ); }, ); } } /// 波浪加载动画 class KRSimpleWave extends StatefulWidget { final Color? color; final double size; final Duration duration; const KRSimpleWave({ super.key, this.color, this.size = 40.0, this.duration = const Duration(milliseconds: 1200), }); @override State createState() => _KRSimpleWaveState(); } class _KRSimpleWaveState extends State with TickerProviderStateMixin { late List _controllers; late List> _animations; @override void initState() { super.initState(); _controllers = List.generate(3, (index) { return AnimationController( duration: widget.duration, vsync: this, ); }); _animations = _controllers.map((controller) { return Tween( begin: 0.0, end: 1.0, ).animate(CurvedAnimation( parent: controller, curve: Curves.easeInOut, )); }).toList(); for (int i = 0; i < _controllers.length; i++) { Future.delayed(Duration(milliseconds: i * 200), () { if (mounted) { _controllers[i].repeat(); } }); } } @override void dispose() { for (var controller in _controllers) { controller.dispose(); } super.dispose(); } @override Widget build(BuildContext context) { return Row( mainAxisSize: MainAxisSize.min, children: List.generate(3, (index) { return AnimatedBuilder( animation: _animations[index], builder: (context, child) { return Container( margin: const EdgeInsets.symmetric(horizontal: 2.0), width: widget.size / 6, height: widget.size * (0.3 + (_animations[index].value * 0.7)), decoration: BoxDecoration( color: widget.color ?? Theme.of(context).primaryColor, borderRadius: BorderRadius.circular(2.0), ), ); }, ); }), ); } }