Flutter 动画集锦之小企鹅跑啊跑

1,280 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第9天,点击查看活动详情

从上一次给大家介绍了自定义单组件布局之后,一直都想写一个关于使用单组件自定义动画的文章. 不过,因为一直没有好的题材,暂且搁置了.今天和对象聊天的时候偶尔发现一个很有意思的动画准备实现一下

起因

这次想做的动画是和对象聊天的时候.对方发的一个表情
gif
相信这个表情很多人应该在wx中也发过, 小企鹅跑来跑去的非常有意思.今天我们就通过 CustomSingleChildLayout 来实现这个动画.

分析

首先我们先分析一下这张图的一个构成.简单来说,就是一个 不规则的图形在来回上下的左右反复运动. 更深入来看, 小企鹅在 移动过程中手的方向也会随移动方向发生改变 . 但是仔细看, 你会发现, 这实际上是x轴反转的一个镜像.
image
知道这些基本点后, 我们就可以开始设计我们的动画了~

Coding

准备

  • 一张透明图片
    ps: 这次演示使用的是一张360*360的图片
    图片名称
  • 前置教程
    如果你发现过程中感觉有些迷茫的话, 不妨静下心先阅读一下关于自定义布局的讲解部分. Flutter 玩转自定义布局之单组件布局

正篇

绘制背景

首先, 我们创建一个居中的100*200的黄色背景

Scaffold(
      body: Center(
        child: Container(
          color: Colors.yellow,
          height: 100,
          width: 200,
        ),
      ),
    );

绘制小企鹅

在Container 内部创建一个CustomSingleChildLayout , 由于外部已经给了限制, 我们这里不需要额外通过_DemoSingleDelegate 的 getSize(BoxConstraints constraints) 去给外部约束尺寸. 同时, 我们创建一个Image 来加载需要的图片. 如果你有自己喜欢的图片, 也可以自行加载, 值得注意的是, 你也需要和本文一样使用一张正方形图片,否则需要根据图片大小自行适配下. 为了方便后面的计算. 我们在 _DemoSingleDelegate 中, 重写getConstraintsForChild(BoxConstraints constraints) 的方法, 返回值为BoxConstraints.tight(Size(100, 100)) . 这样便可以将小企鹅约束在100*100 的范围内.

    CustomSingleChildLayout(
        delegate: _DemoSingleDelegate(),
        child: Image.asset(
            'assets/images/qiuqiu.png',
            height: 100,
            width: 100,
        ),
    ),

效果
image

小企鹅动起来

在小企鹅绘制到方块中后, 我们怎么让它动起来呢? 这里需 借助AnimationController, 它的作用主要是推动小企鹅运动. 我们知道 AnimationController 有个repeat属性, 天然解决了小企鹅在运动中需要来回奔跑的问题.

    AnimationController controller = 
        AnimationController(vsync: this, duration: const Duration(seconds: 1));

    /// 重复运动
    controller.repeat(reverse: true);
    

那么, 如何控制小企鹅的位置呢? 我们可以通过 AnimationController 使用动画器Animation, Animattion可以自定义动画的取值和范围. 这里我们先创建一个Animation

/// 用来记录小企鹅的位移
class _DemoInfo {
  double x;
  double y;

  _DemoInfo(this.x, this.y);
}

class _DemoAnimation extends Animatable<_DemoInfo> {
  @override
  _DemoInfo transform(double t) {
    ...
  }
}

创建Animation 之后, 我们发现它内部提供了一个 transform(double t) 的方法. 这里的 t 即 AnimationController的value, 也就是当前动画执行的进度. 我们根据进度来反推小企鹅的位置. 实际上小企鹅的位置很像函数中的正弦函数 image 我们如果按正常的正弦函数的话 实际上我们只有上下移动一次. 正弦的公式是 y = A sin(Bx + C) + D. 周期的计算公式是 2π/B . 也就是说我们 B 越大, 同时间内,周期越短,上下移动的频次就越高. 我们以 B 等于 6 为例: image 其中,蓝色为 B 等于 1 的时候,红色为 B 等于 6 的时候. 我们同时间内可以上下移动 6 次, 了解到了这一点后. 我们就可以根据 AnimationController的value 来计算小企鹅相应的位置了.

    // 由于小企鹅左右还有空白的部分,我们实际计算中还要减去这部分.
    // 因此x轴要从-25开始计算, y轴我们可以自定义初始的高度, 然后
    // 改变振幅,也就是公式中的A. 这样我们上下偏移的距离分别就是10.
    return _DemoInfo(-25 + 150.0 * t, 30 + 10 * sin(t * 6 * pi));

计算好小企鹅的位置后, 我们就要同步自定义布局的_DemoSingleDelegate . 我们把 Animation 传入_DemoSingleDelegate .

  Animation<_DemoInfo>? animation;

  _DemoSingleDelegate({required this.animation}) : super(relayout: animation);

这样当动画执行时, 我们就可以同步更新画布了. 让我们来看看小企鹅跑起来什么样吧! gif

小企鹅转向

我们发现在运动过程中,小企鹅并不会在走到头的时候转向. 反转的本质是对小企鹅的x轴反转, 我们来实现一下.

    AnimatedBuilder(
        builder: (BuildContext context, Widget? child) {
            return Transform.scale(
                /// 这里当动画forward运行时,小企鹅往左跑. 在reverse时, 小企鹅右跑
                scaleX:animation.status == AnimationStatus.forward ? 1 : -1,
                child: child,
            );
        },
        animation: animation,
        child: Image.asset(
            'assets/images/qiuqiu.png',
            height: 100,
            width: 100,
        ),
    ),

效果
gif

完善界面

小企鹅转向后, 我们发现小企鹅的脚是超出色块的. 这个需要我们手动裁切一下, 我们在 CustomSingleChildLayout 外加上一个ClipRect 就可以啦!

gif

源码

github.com/weniner/flu…

结语

这里是WeninerIo,热爱生活且热爱旅行.如果你对这次的分享感兴趣又或者有什么疑惑, 不妨评论区留言 + 关注.期待下一次更好的相遇.