思路参考自: 扔物线java
话很少少,直接上效果git
经过观察能够发现这个动画分为三个过程github
过程三:右边翘起来ide
图片绕着 x 轴旋转,左侧视图为旋转后投影到二位平面的图片,右侧为旋转过程当中的三维视图。布局
能够把图片分红上下两部分,上半边彻底没动,下半部分绕着 x 轴旋转,不断改变转动角度就能够达到过程一的效果动画
过程二稍复杂,先看其中某一帧的状况ui
红线下半部分翘起来了,上半部分没有翘起来,因此考虑分为上下两部分绘制this
把这两部分图拼接起来就是过程二中某一帧的效果lua
保持每一帧 绕着 x 轴旋转的角度固定,改变绕着 z 轴旋转的角度就能够实现过程二的动画。spa
不断改变 x 轴旋转的角度就能够就能够实现过程一中下半部分的动画效果
过程三和过程一相似,再也不赘述。
过程一:
过程二:
过程三
首先定义一个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指定具体的裁剪范围。
源码点这里 喜欢的话 star 哦