软件编程
位置:首页>> 软件编程>> Android编程>> Flutter实现牛顿摆动画效果的示例代码

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

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

标签:Flutter,牛顿摆,动画

前言

牛顿摆大家应该都不陌生,也叫碰碰球、永动球(理论情况下),那么今天我们用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

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com