思路参考自: 扔物线

整体效果

话不多少,直接上效果

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

  • 过程一: 底部翘起来

  • 过程二: 转起来

过程三:右边翘起来

三维图像投影到二维平面

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

过程一

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

过程二

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

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

下半部分

  1. 图片绕着 z 轴旋转 20 度
  2. 裁剪图片,只取下半部分
  3. 图片绕着 x 轴旋转 45 度
  4. 图片绕着 z 轴旋转 -20 度

上半部分

  1. 图片绕着 z 轴旋转 20 度
  2. 裁剪图片,只取上半部分
  3. 图片绕着 x 轴旋转 0 度(为什么?为了和其他过程统一过程,方便代码编写)
  4. 图片绕着 z 轴旋转 -20 度

拼接

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

实现过程二的动画

保持每一帧 绕着 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;@overridevoid 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();}@overrideWidget build(BuildContext context) {return AnimateFlipWidget(animation: animation,child: imageWidget,);}@overridevoid 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);@overrideWidget 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);@overrideRect getClip(Size size) {return new Rect.fromLTRB(-size.width, size.height / 2, size.width * 2, size.height * 2);}@overridebool shouldReclip(CustomClipper<Rect> oldClipper) {return true;}
}

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

源码

源码点这里
喜欢的话 star 哦

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

  1. PWA入门:手把手教你制作一个PWA应用

    摘要: PWA图文教程 原文:PWA入门:手把手教你制作一个PWA应用 作者:MudOnTire Fundebug经授权转载,版权归原作者所有. 简介 Web前端的同学是否想过学习app开发,以弥补自 ...

  2. 手把手教你写一个生成对抗网络

    成对抗网络代码全解析, 详细代码解析(TensorFlow, numpy, matplotlib, scipy) 那么,什么是 GANs? 用 Ian Goodfellow 自己的话来说: " ...

  3. python手机版做小游戏代码大全-Python大牛手把手教你做一个小游戏,萌新福利!...

    原标题:Python大牛手把手教你做一个小游戏,萌新福利! 引言 最近python语言大火,除了在科学计算领域python有用武之地之外,在游戏.后台等方面,python也大放异彩,本篇博文将按照正规 ...

  4. python k线合成_手把手教你写一个Python版的K线合成函数

    手把手教你写一个Python版的K线合成函数 在编写.使用策略时,经常会使用一些不常用的K线周期数据.然而交易所.数据源又没有提供这些周期的数据.只能通过使用已有周期的数据进行合成.合成算法已经有一个 ...

  5. 第五十八期:从0到1 手把手教你建一个区块链

    近期的区块链重回热点,如果你想深入了解区块链,那就来看一下本文,手把手教你构建一个自己的区块链. 作者:Captain编译 近期的区块链重回热点,如果你想深入了解区块链,那就来看一下本文,手把手教你构 ...

  6. 手把手教你写一个spring IOC容器

    本文分享自华为云社区<手把手教你写一个spring IOC容器>,原文作者:技术火炬手. spring框架的基础核心和起点毫无疑问就是IOC,IOC作为spring容器提供的核心技术,成功 ...

  7. 手把手教你撸一个Web汇率计算器

    手把手教你撸一个Web汇率计算器 前言 前段时间刚接触到前端网页开发,但是对于刚入门的小白而言,像flask.Django等这类稍大型的框架确实不太适合,今天这个Dash是集众家之长于一体的轻量化We ...

  8. 手把手教你写一个Matlab App(一)

    对于传统工科的学生用的最多的编程软件应该就是matlab,其集成度高,计算能力强,容易上手,颇受大众青睐.今天挖的这个新坑,主要是分享用matlab app designer设计GUI界面的一些方法和 ...

  9. 还没理解微前端?手把手教你实现一个迷你版

    大厂技术  高级前端  Node进阶 点击上方 程序员成长指北,关注公众号 回复1,加入高级Node交流群 最近看了几个微前端框架的源码(single-spa[1].qiankun[2].micro- ...

最新文章

  1. 小麦的一生矢量图收藏贴-从种子的萌发到完熟
  2. Arabidopsis thaliana 拟南芥 长read SRX533608
  3. 平衡自动化与智能化,构建AI生态系统
  4. CEPH添加MDS操作
  5. JavaScript 把字符串类型转换成日期类型
  6. Linux下的gdb调试makefile的编写
  7. 字符串与字符串函数 - 字符串输出 常用字符串函数
  8. 【机器视觉】 dev_set_shape算子
  9. python数据库sqlite3_Python数据库之SQLite3
  10. Spring Cloud Alibaba基础教程:Nacos的数据持久化 1
  11. 全民战“疫”,ZStack ZCCT在线认证疫情期间免费开放!
  12. 一图区分1.85mm/2.4mm/2.92mm/3.5mm/SMA射频接头
  13. Mybatis 注解@select,@detele,@update,@insert的简单应用
  14. 如何将Kali Linux中的Firefox ESR浏览器语言设置为中文
  15. Bootstrap 3 学习
  16. 20多岁的年纪,做什么将来才不后悔?
  17. python怎么把照片转成卡通_如何把照片变成手绘动漫化?
  18. 如何让开源多点成功的几率;开源和 COVID-19: 道高一尺魔高一丈;等开源之道每周评论2020 04 07...
  19. jk触发器改为四进制_异步计数器 || 计数器的分类 ||异步二进制十进制|| 74290 8421 5421 || 数电...
  20. fprintf 函数详解

热门文章

  1. 高斯列选主元素消元法
  2. Python基础篇13:修改字符串
  3. COLORREF、COLOR、RGB转化总结分析及在VC++中的使用
  4. webgame创意之《超时空要塞之边境》
  5. Android LinearLayout 各布局属性总结
  6. 手机网游开发基础知识之Wap协议
  7. 自我介绍INTRODUCE
  8. Q币直充-迅银渠道商(php 面向对象类)
  9. iOS备忘录之如何去掉苹果自带输入法输入英文时的“空格”
  10. css 多行文字换行