内容简介:话不多少,直接上效果通过观察可以发现这个动画分为三个过程
思路参考自: 扔物线
整体效果
话不多少,直接上效果
通过观察可以发现这个动画分为三个过程
- 过程一: 底部翘起来
- 过程二: 转起来
过程三:右边翘起来
三维图像投影到二维平面
图片绕着 x 轴旋转,左侧视图为旋转后投影到二位平面的图片,右侧为旋转过程中的三维视图。
过程一
可以把图片分成上下两部分,上半边完全没动,下半部分绕着 x 轴旋转,不断改变转动角度就可以达到过程一的效果
过程二
过程二稍复杂,先看其中某一帧的情况
红线下半部分翘起来了,上半部分没有翘起来,所以考虑分为上下两部分绘制
下半部分
- 图片绕着 z 轴旋转 20 度
- 裁剪图片,只取下半部分
- 图片绕着 x 轴旋转 45 度
- 图片绕着 z 轴旋转 -20 度
上半部分
- 图片绕着 z 轴旋转 20 度
- 裁剪图片,只取上半部分
- 图片绕着 x 轴旋转 0 度(为什么?为了和其他过程统一过程,方便代码编写)
- 图片绕着 z 轴旋转 -20 度
拼接
把这两部分图拼接起来就是过程二中某一帧的效果
实现过程二的动画
保持每一帧 绕着 x 轴旋转的角度固定,改变绕着 z 轴旋转的角度就可以实现过程二的动画。
改进过程一(方便代码编写)
过程一下半部分
- 图片绕着 z 轴旋转 0 度
- 裁剪图片,只取下半部分
- 图片绕着 x 轴旋转某个角度
- 图片绕着 z 轴旋转 0 度
不断改变 x 轴旋转的角度就可以就可以实现过程一中下半部分的动画效果
过程一上半部分
- 图片绕着 z 轴旋转 0 度
- 裁剪图片,只取上半部分
- 图片绕着 x 轴旋转 0 度
- 图片绕着 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指定具体的裁剪范围。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- CSS3实现卡片翻转动画效果
- 简单几步,用纯CSS3实现3D翻转效果!
- 神奇的数字“3”:一次翻转3个比特即可实现Rowhammer攻击
- canvas 图像旋转与翻转姿势解锁
- canvas 图像旋转与翻转姿势解锁
- 图解:K 个一组翻转链表
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。