利用flutter写了个类似于充电的动画,比如光伏板给电池充电,模仿电力输送的过程,效果如下图所示
下面直接贴出代码,属性都标注了注释,使用的时候在外面加个盒子固定宽高,然后给出每个点的坐标,内部会根据坐标的点数来判断有几根线,然后画出线的路径,进行动画模拟,可以根据项目需求来自定义点的颜色,每条路径上点的个数以及点的大小等等,
import 'package:flutter/material.dart';
class ElectricAnimated extends StatelessWidget {
final int duration; //动画时长
final List<Offset> path; //路径的点坐标
const ElectricAnimated({
super.key,
this.duration = 5,
required this.path,
});
@override
Widget build(BuildContext context) {
return Stack(
children: List.generate(
path.length - 1,
(index) {
return AnimatedDots(
path: [
path[index],
path[index + 1],
],
dotCount: 3,
duration: Duration(
seconds: duration,
),
);
},
),
);
}
}
class AnimatedDots extends StatefulWidget {
final List<Offset> path; //路径的点坐标
final int dotCount; //每条路径上的点的个数
final double dotSize; //点的大小
final Duration duration;
final Color dotColor; //点的颜色
const AnimatedDots({
Key? key,
required this.path,
this.dotCount = 5,
this.dotSize = 2,
this.duration = const Duration(seconds: 2),
this.dotColor = Colors.white,
}) : super(key: key);
@override
State<AnimatedDots> createState() => _AnimatedDotsState();
}
class _AnimatedDotsState extends State<AnimatedDots>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _animation;
int _currentIndex = 0;
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: widget.duration,
);
_animation = Tween<double>(
begin: 0,
end: 1,
).animate(_animationController);
_animationController.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_currentIndex++;
if (_currentIndex >= widget.path.length - 1) {
_currentIndex = 0;
}
_animationController.reset();
_animationController.forward();
}
});
_animationController.forward();
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
final value = _animation.value;
final current = widget.path[_currentIndex];
final next = widget.path[(_currentIndex + 1) % widget.path.length];
final dots = List.generate(widget.dotCount, (index) {
final dotValue = (value + index / widget.dotCount) % 1;
final x = current.dx + (next.dx - current.dx) * dotValue;
final y = current.dy + (next.dy - current.dy) * dotValue;
return Positioned(
left: x - widget.dotSize / 2,
top: y - widget.dotSize / 2,
child: Container(
width: widget.dotSize,
height: widget.dotSize,
decoration: BoxDecoration(
color: widget.dotColor,
shape: BoxShape.circle,
),
),
);
});
return Stack(
children: dots,
);
},
);
}
}
使用
body: Center(
child: Container(
color: Colors.black,
width: 150,
height: 150,
child: const ElectricAnimated(
duration: 3,
path: [
Offset(30, 0),
Offset(30, 67),
Offset(90, 67),
Offset(90, 137),
],
),
),
),
简书地址