Flutter:手把手教你实现一个仿 Flipboard 图片3D翻转动画

栏目: IOS · Android · 发布时间: 5年前

内容简介:话不多少,直接上效果通过观察可以发现这个动画分为三个过程

思路参考自: 扔物线

整体效果

话不多少,直接上效果

Flutter:手把手教你实现一个仿 Flipboard 图片3D翻转动画

通过观察可以发现这个动画分为三个过程

  • 过程一: 底部翘起来
Flutter:手把手教你实现一个仿 Flipboard 图片3D翻转动画
  • 过程二: 转起来
Flutter:手把手教你实现一个仿 Flipboard 图片3D翻转动画

过程三:右边翘起来

Flutter:手把手教你实现一个仿 Flipboard 图片3D翻转动画

三维图像投影到二维平面

图片绕着 x 轴旋转,左侧视图为旋转后投影到二位平面的图片,右侧为旋转过程中的三维视图。

Flutter:手把手教你实现一个仿 Flipboard 图片3D翻转动画

过程一

可以把图片分成上下两部分,上半边完全没动,下半部分绕着 x 轴旋转,不断改变转动角度就可以达到过程一的效果

Flutter:手把手教你实现一个仿 Flipboard 图片3D翻转动画

过程二

过程二稍复杂,先看其中某一帧的情况

Flutter:手把手教你实现一个仿 Flipboard 图片3D翻转动画

红线下半部分翘起来了,上半部分没有翘起来,所以考虑分为上下两部分绘制

下半部分

Flutter:手把手教你实现一个仿 Flipboard 图片3D翻转动画
  1. 图片绕着 z 轴旋转 20 度
  2. 裁剪图片,只取下半部分
  3. 图片绕着 x 轴旋转 45 度
  4. 图片绕着 z 轴旋转 -20 度

上半部分

Flutter:手把手教你实现一个仿 Flipboard 图片3D翻转动画
  1. 图片绕着 z 轴旋转 20 度
  2. 裁剪图片,只取上半部分
  3. 图片绕着 x 轴旋转 0 度(为什么?为了和其他过程统一过程,方便代码编写)
  4. 图片绕着 z 轴旋转 -20 度

拼接

Flutter:手把手教你实现一个仿 Flipboard 图片3D翻转动画 Flutter:手把手教你实现一个仿 Flipboard 图片3D翻转动画

把这两部分图拼接起来就是过程二中某一帧的效果

实现过程二的动画

保持每一帧 绕着 x 轴旋转的角度固定,改变绕着 z 轴旋转的角度就可以实现过程二的动画。

改进过程一(方便代码编写)

过程一下半部分

  1. 图片绕着 z 轴旋转 0 度
  2. 裁剪图片,只取下半部分
  3. 图片绕着 x 轴旋转某个角度
  4. 图片绕着 z 轴旋转 0 度

不断改变 x 轴旋转的角度就可以就可以实现过程一中下半部分的动画效果

过程一上半部分

  1. 图片绕着 z 轴旋转 0 度
  2. 裁剪图片,只取上半部分
  3. 图片绕着 x 轴旋转 0 度
  4. 图片绕着 z 轴旋转 0 度

过程三

过程三和过程一类似,不再赘述。

整个动画具体参数

  • 过程一:

    • 上半部分:旋转角度都是 0
    • 下半部分:绕 z 轴旋转角度始终为 0,绕 x 轴旋转角度从 0 过渡到 -45 度
  • 过程二:

    • 上半部分:绕着 z 轴旋转角度从 0 过渡到270 度,绕着 x 轴旋转的角度固定为 0 度
    • 下半部分:绕着 z 轴旋转角度从 0 过渡到270 度,绕着 x 轴旋转的角度固定为 -45 度
  • 过程三

    • 上半部分:绕 z 轴旋转角度始终为 270 度,绕 x 轴旋转角度从 0 过渡到 45 度
    • 下半部分:绕 z 轴旋转角度始终为 270 度,绕 x 轴旋转角度始终为 0 度

代码编写

首先定义一个enum,标识动画当前进行到那个过程

enum FlipAnimationSteps { animation_step_1, animation_step_2, animation_step_3 }
复制代码

设置动画参数,监听动画状态

class _FlipAnimationApp extends State<FlipAnimationApp>
    with SingleTickerProviderStateMixin {
  var imageWidget = Image.asset(
    'images/mario.jpg',
    width: 300.0,
    height: 300.0,
  );

  AnimationController controller;

  CurvedAnimation animation;

  @override
  void initState() {
    super.initState();
    controller =
        AnimationController(duration: const Duration(seconds: 1), vsync: this);
    animation = CurvedAnimation(
      parent: controller,
      curve: Curves.easeInOut,
    )..addStatusListener((status) {
        if (status == AnimationStatus.completed) {
          switch (currentFlipAnimationStep) {
            case FlipAnimationSteps.animation_step_1:
              currentFlipAnimationStep = FlipAnimationSteps.animation_step_2;
              controller.reset();
              controller.forward();
              break;

            case FlipAnimationSteps.animation_step_2:
              currentFlipAnimationStep = FlipAnimationSteps.animation_step_3;
              controller.reset();
              controller.forward();
              break;
            case FlipAnimationSteps.animation_step_3:
              break;
          }
        }
      });

    controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return AnimateFlipWidget(
      animation: animation,
      child: imageWidget,
    );
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }
}

复制代码

再来看看核心类 AnimateFlipWidget ,动画相关的主要逻辑都在里面。

class AnimateFlipWidget extends AnimatedWidget {
  final Widget child;

  double _currentTopRotationXRadian = 0;
  double _currentBottomRotationXRadian = 0;
  double _currentRotationZRadian = 0;

  static final _topRotationXRadianTween =
      Tween<double>(begin: 0, end: math.pi / 4);
  static final _bottomRotationXRadianTween =
      Tween<double>(begin: 0, end: -math.pi / 4);
  static final _rotationZRadianTween =
      Tween<double>(begin: 0, end: (1 + 1 / 2) * math.pi);

  AnimateFlipWidget({Key key, Animation<double> animation, this.child})
      : super(key: key, listenable: animation);

  @override
  Widget build(BuildContext context) {
    final Animation<double> animation = listenable;

    return Center(
      child: Container(
        child: Stack(
          children: [
            Transform(
              alignment: Alignment.center,
              transform: Matrix4.rotationZ(currentFlipAnimationStep ==
                      FlipAnimationSteps.animation_step_2
                  ? _rotationZRadianTween.evaluate(animation) * -1
                  : _currentRotationZRadian * -1),
              child: Transform(
                transform: Matrix4.identity()
                  ..setEntry(3, 2, 0.002)
                  ..rotateX(currentFlipAnimationStep ==
                          FlipAnimationSteps.animation_step_3
                      ? _currentTopRotationXRadian =
                          _topRotationXRadianTween.evaluate(animation)
                      : _currentTopRotationXRadian),
                alignment: Alignment.center,
                child: ClipRect(
                  clipper: _TopClipper(context),
                  child: Transform(
                    alignment: Alignment.center,
                    transform: Matrix4.rotationZ(currentFlipAnimationStep ==
                            FlipAnimationSteps.animation_step_2
                        ? _currentRotationZRadian =
                            _rotationZRadianTween.evaluate(animation)
                        : _currentRotationZRadian),
                    child: child,
                  ),
                ),
              ),
            ),
            Transform(
              alignment: Alignment.center,
              transform: Matrix4.rotationZ(currentFlipAnimationStep ==
                      FlipAnimationSteps.animation_step_2
                  ? _rotationZRadianTween.evaluate(animation) * -1
                  : _currentRotationZRadian * -1),
              child: Transform(
                transform: Matrix4.identity()
                  ..setEntry(3, 2, 0.002)
                  ..rotateX(currentFlipAnimationStep ==
                          FlipAnimationSteps.animation_step_1
                      ? _currentBottomRotationXRadian =
                          _bottomRotationXRadianTween.evaluate(animation)
                      : _currentBottomRotationXRadian),
                alignment: Alignment.center,
                child: ClipRect(
                  clipper: _BottomClipper(context),
                  child: Transform(
                    alignment: Alignment.center,
                    transform: Matrix4.rotationZ(currentFlipAnimationStep ==
                            FlipAnimationSteps.animation_step_2
                        ? _currentRotationZRadian =
                            _rotationZRadianTween.evaluate(animation)
                        : _currentRotationZRadian),
                    child: child,
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

复制代码

这个类返回了一个 Stack 布局,可以把上半部分和下半部分的变换结果叠加在一起(注意:不能用 Column 布局哦), children 里面的两个 Transform 就是上下两部分变化之后的结果。可以发现两个 Transform 都是符合前面的变换流程(绕 Z 轴旋转 - > 裁剪 -> 绕 X 轴旋转 -> 绕 Z 轴转回来)。

看一下下半部分裁剪的过程

class _BottomClipper extends CustomClipper<Rect> {
  final BuildContext context;

  _BottomClipper(this.context);

  @override
  Rect getClip(Size size) {
    return new Rect.fromLTRB(
        -size.width, size.height / 2, size.width * 2, size.height * 2);
  }

  @override
  bool shouldReclip(CustomClipper<Rect> oldClipper) {
    return true;
  }
}
复制代码

定义一个类,继承CustomClipper类,重写getClip指定具体的裁剪范围。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Web性能优化

Web性能优化

Patrick Killelea 谢 / 谢文亮 / 清华大学出版社 / 2003-11-01 / 49.00元

本书讲述如何将Web性能调至最佳状态。书中不仅谈到了Web服务器软件的优化,而且还涉及到如何流水化处理Web内容,如何从浏览器端着手优化性能,如何调校客户端和服务器的硬件,以及如何最大限度地使用网络本身的特性。 书中的内容涉及到影响性能好坏的本质,并为得到立竿见影的效果提供了具体建议。本书向您娓娓道出评价计算性能高低的准则,并在后半部分讲述从客户端、网络直到服务器这一链条中每个环节的薄弱之一起来看看 《Web性能优化》 这本书的介绍吧!

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具