Flutter实现牛顿摆动画效果的示例代码
作者:老李code 时间:2022-10-23 12:58:40
前言
牛顿摆大家应该都不陌生,也叫碰碰球、永动球(理论情况下),那么今天我们用Flutter实现这么一个理论中的永动球,可以作为加载Loading使用。
- 知识点:绘制、动画曲线、多动画状态更新
效果图:
实现步骤
1、绘制静态效果
首先我们需要把线和小圆球绘制出来,对于看过我之前文章的小伙伴来说这个就很简单了,效果图:
关键代码:
// 小圆球半径
double radius = 6;
/// 小球圆心和直线终点一致
//左边小球圆心
Offset offset = Offset(20, 60);
//右边小球圆心
Offset offset2 = Offset(20 * 6 * 8, 60);
Paint paint = Paint()
..color = Colors.black87
..strokeWidth = 2;
/// 绘制线
canvas.drawLine(Offset.zero, Offset(90, 0), paint);
canvas.drawLine(Offset(20, 0), offset, paint);
canvas.drawLine(
Offset(20 + radius * 2, 0), Offset(20 + radius * 2, 60), paint);
canvas.drawLine(
Offset(20 + radius * 4, 0), Offset(20 + radius * 4, 60), paint);
canvas.drawLine(
Offset(20 + radius * 6, 0), Offset(20 + radius * 6, 60), paint);
canvas.drawLine(Offset(20 + radius * 8, 0), offset2, paint);
/// 绘制小圆球
canvas.drawCircle(offset, radius, paint);
canvas.drawCircle(Offset(20 + radius * 2, 60), radius, paint);
canvas.drawCircle(Offset(20 + radius * 4, 60), radius, paint);
canvas.drawCircle(Offset(20 + radius * 6, 60), radius, paint);
canvas.drawCircle(offset2, radius, paint);
2、加入动画
思路: 我们可以看到5个小球一共2个小球在运动,左边小球运动一个来回之后传递给右边小球,右边小球开始运动,右边一个来回再传递给左边开始,也就是左边运动周期是:0-1-0,正向运动一次,反向再运动一次,这样就是一个周期,右边也是一样,左边运动完传递给右边,右边运动完传递给左边,这样就简单实现了牛顿摆的效果。
两个关键点
小球运动路径: 小球的运动路径是一个弧度,以竖线的起点为圆心,终点为半径,那么我们只需要设置小球运动至最高点的角度即可,通过角度就可计算出小球的坐标点。
运动曲线: 当然我们知道牛顿摆小球的运动曲线并不是匀速的,他是有一个加速减速过程的,撞击之后,小球先加速然后减速达到最高点速度为0,之后速度再从0慢慢加速进行撞击小球,周而复始。
下面的运动曲线就是先加速再减速,大概符合牛顿摆的运动曲线。我们就使用这个曲线看看效果。
完整源码
class OvalLoading extends StatefulWidget {
const OvalLoading({Key? key}) : super(key: key);
@override
_OvalLoadingState createState() => _OvalLoadingState();
}
class _OvalLoadingState extends State<OvalLoading>
with TickerProviderStateMixin {
// 左边小球
late AnimationController _controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 300))
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller.reverse(); //反向执行 1-0
} else if (status == AnimationStatus.dismissed) {
_controller2.forward();
}
})
..forward();
// 右边小球
late AnimationController _controller2 =
AnimationController(vsync: this, duration: Duration(milliseconds: 300))
..addStatusListener((status) {
// dismissed 动画在起始点停止
// forward 动画正在正向执行
// reverse 动画正在反向执行
// completed 动画在终点停止
if (status == AnimationStatus.completed) {
_controller2.reverse(); //反向执行 1-0
} else if (status == AnimationStatus.dismissed) {
// 反向执行完毕左边小球执行
_controller.forward();
}
});
late var cure =
CurvedAnimation(parent: _controller, curve: Curves.easeOutCubic);
late var cure2 =
CurvedAnimation(parent: _controller2, curve: Curves.easeOutCubic);
late Animation<double> animation = Tween(begin: 0.0, end: 1.0).animate(cure);
late Animation<double> animation2 =
Tween(begin: 0.0, end: 1.0).animate(cure2);
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsetsDirectional.only(top: 300, start: 150),
child: CustomPaint(
size: Size(100, 100),
painter: _OvalLoadingPainter(
animation, animation2, Listenable.merge([animation, animation2])),
),
);
}
@override
void dispose() {
_controller.dispose();
_controller2.dispose();
super.dispose();
}
}
class _OvalLoadingPainter extends CustomPainter {
double radius = 6;
final Animation<double> animation;
final Animation<double> animation2;
final Listenable listenable;
late Offset offset; // 左边小球圆心
late Offset offset2; // 右边小球圆心
final double lineLength = 60; // 线长
_OvalLoadingPainter(this.animation, this.animation2, this.listenable)
: super(repaint: listenable) {
offset = Offset(20, lineLength);
offset2 = Offset(20 * radius * 8, lineLength);
}
// 摆动角度
double angle = pi / 180 * 30; // 30°
@override
void paint(Canvas canvas, Size size) {
Paint paint = Paint()
..color = Colors.black87
..strokeWidth = 2;
// 左边小球 默认坐标 下方是90度 需要+pi/2
var dx = 20 + 60 * cos(pi / 2 + angle * animation.value);
var dy = 60 * sin(pi / 2 + angle * animation.value);
// 右边小球
var dx2 = 20 + radius * 8 - 60 * cos(pi / 2 + angle * animation2.value);
var dy2 = 60 * sin(pi / 2 + angle * animation2.value);
offset = Offset(dx, dy);
offset2 = Offset(dx2, dy2);
/// 绘制线
canvas.drawLine(Offset.zero, Offset(90, 0), paint);
canvas.drawLine(Offset(20, 0), offset, paint);
canvas.drawLine(
Offset(20 + radius * 2, 0), Offset(20 + radius * 2, 60), paint);
canvas.drawLine(
Offset(20 + radius * 4, 0), Offset(20 + radius * 4, 60), paint);
canvas.drawLine(
Offset(20 + radius * 6, 0), Offset(20 + radius * 6, 60), paint);
canvas.drawLine(Offset(20 + radius * 8, 0), offset2, paint);
/// 绘制球
canvas.drawCircle(offset, radius, paint);
canvas.drawCircle(
Offset(20 + radius * 2, 60),
radius,
paint);
canvas.drawCircle(Offset(20 + radius * 4, 60), radius, paint);
canvas.drawCircle(Offset(20 + radius * 6, 60), radius, paint);
canvas.drawCircle(offset2, radius, paint);
}
@override
bool shouldRepaint(covariant _OvalLoadingPainter oldDelegate) {
return oldDelegate.listenable != listenable;
}
}
去掉线的效果
来源:https://juejin.cn/post/7090123854135164935
标签:Flutter,牛顿摆,动画
0
投稿
猜你喜欢
详解Java的位操作符
2023-06-24 05:31:14
详解spring boot集成ehcache 2.x 用于hibernate二级缓存
2023-06-25 05:49:44
Android Flutter实现搜索的三种方式详解
2023-07-10 18:00:49
android 触屏的震动响应接口调用方法
2021-08-04 08:33:34
一文带你熟练掌握Java中的日期时间相关类
2022-01-21 00:42:54
C#实现为视频添加水印
2022-02-16 05:30:07
java的三种随机数生成方式
2022-03-06 13:43:57
C#中using语句的用法
2023-07-02 05:33:24
springmvc处理模型数据Map过程解析
2022-04-24 01:06:01
Java实现画图 给图片底部添加文字标题
2023-03-28 23:23:06
springboot使用log4j2异步日志提升性能的实现方式
2021-05-31 16:54:55
Spring Boot 在启动时进行配置文件加解密的方法详解
2023-11-12 17:13:54
Android自定义view实现圆环效果实例代码
2022-05-22 10:17:25
C#实现鼠标消息捕获
2021-06-14 20:38:38
Java VisualVM监控远程JVM(详解)
2022-07-10 15:59:15
Android序列化接口Parcelable与Serializable接口对比
2023-03-24 17:48:59
Java8如何从一个Stream中过滤null值
2022-02-03 08:10:20
关于@Scheduled不执行的原因分析
2021-06-27 17:13:20
Java开发中可以防止界面假死的刷新代码
2023-11-23 22:23:41
Mybatis逆向工程运行代码实例
2021-06-22 04:54:59