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

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

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

思路参考自: 扔物线

整体效果

话不多少,直接上效果

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指定具体的裁剪范围。


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

查看所有标签

猜你喜欢:

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

The Sovereign Individual

The Sovereign Individual

James Dale Davidson、William Rees-Mogg / Free Press / 1999-08-26 / USD 16.00

Two renowned investment advisors and authors of the bestseller The Great Reckoning bring to light both currents of disaster and the potential for prosperity and renewal in the face of radical changes ......一起来看看 《The Sovereign Individual》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具