Some checks failed
Build Android APK / 编译 libcore.aar (push) Has been cancelled
Build Android APK / 编译 Android APK (release) (push) Has been cancelled
Build Android APK / 创建 GitHub Release (push) Has been cancelled
Build Multi-Platform / 编译 libcore (Android) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (Windows) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (macOS) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (Linux) (push) Has been cancelled
Build Multi-Platform / 构建 Android APK (push) Has been cancelled
Build Multi-Platform / 构建 Windows (push) Has been cancelled
Build Multi-Platform / 构建 macOS (push) Has been cancelled
Build Multi-Platform / 构建 Linux (push) Has been cancelled
Build Multi-Platform / 创建 Release (push) Has been cancelled
Build Windows / build (push) Has been cancelled
281 lines
7.5 KiB
Dart
Executable File
281 lines
7.5 KiB
Dart
Executable File
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<KRSimpleLoading> createState() => _KRSimpleLoadingState();
|
|
}
|
|
|
|
class _KRSimpleLoadingState extends State<KRSimpleLoading>
|
|
with SingleTickerProviderStateMixin {
|
|
late AnimationController _controller;
|
|
late Animation<double> _rotationAnimation;
|
|
|
|
// 环的宽度 5px
|
|
static const double _strokeWidth = 5.0;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
|
|
_controller = AnimationController(
|
|
duration: widget.duration,
|
|
vsync: this,
|
|
);
|
|
|
|
_rotationAnimation = Tween<double>(
|
|
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<Color>(
|
|
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<KRSimplePulse> createState() => _KRSimplePulseState();
|
|
}
|
|
|
|
class _KRSimplePulseState extends State<KRSimplePulse>
|
|
with SingleTickerProviderStateMixin {
|
|
late AnimationController _controller;
|
|
late Animation<double> _animation;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_controller = AnimationController(
|
|
duration: widget.duration,
|
|
vsync: this,
|
|
);
|
|
_animation = Tween<double>(
|
|
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<KRSimpleWave> createState() => _KRSimpleWaveState();
|
|
}
|
|
|
|
class _KRSimpleWaveState extends State<KRSimpleWave>
|
|
with TickerProviderStateMixin {
|
|
late List<AnimationController> _controllers;
|
|
late List<Animation<double>> _animations;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_controllers = List.generate(3, (index) {
|
|
return AnimationController(
|
|
duration: widget.duration,
|
|
vsync: this,
|
|
);
|
|
});
|
|
|
|
_animations = _controllers.map((controller) {
|
|
return Tween<double>(
|
|
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),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}),
|
|
);
|
|
}
|
|
}
|