Flutter实现牛顿摆动画效果的示例代码

作者:老李code 时间:2022-10-23 12:58:40 

前言

牛顿摆大家应该都不陌生,也叫碰碰球、永动球(理论情况下),那么今天我们用Flutter实现这么一个理论中的永动球,可以作为加载Loading使用。

- 知识点:绘制、动画曲线、多动画状态更新

效果图:

Flutter实现牛顿摆动画效果的示例代码

实现步骤

1、绘制静态效果

首先我们需要把线和小圆球绘制出来,对于看过我之前文章的小伙伴来说这个就很简单了,效果图:

Flutter实现牛顿摆动画效果的示例代码

关键代码:

// 小圆球半径
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慢慢加速进行撞击小球,周而复始。

下面的运动曲线就是先加速再减速,大概符合牛顿摆的运动曲线。我们就使用这个曲线看看效果。

Flutter实现牛顿摆动画效果的示例代码

完整源码

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;
 }
}

去掉线的效果

Flutter实现牛顿摆动画效果的示例代码

来源: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
  • asp之家 软件编程 m.aspxhome.com