Flutter教程:利用Flipboard风格的图片实现3D翻转动画
通过观察可以看到,这个动画分为两个过程
- 过程一:底部向上倾斜
- 过程二:向上翻转
过程三:右侧向上倾斜
将三维图像投影到二维平面上
图像绕x轴旋转。左视图是旋转后投影到二维平面上的图像,右视图是旋转过程中的三维视图。
处理一
您可以将图像分割为顶部和底部。上半部分根本不动,下半部分绕x轴旋转。通过不断改变旋转角度,可以实现流程一
流程二
第二个流程稍微复杂一点。我们先看单帧情况。
红线的下半部分向上倾斜,但上半部分则没有。因此,请考虑将其分成两部分来绘制
下半部分

- 图像环绕 绕 z 轴旋转 20 度
- 裁剪图像并仅删除一半
- 将图像旋转 45 度乘 45 度。 -axis
- 将图像绕 z 轴旋转 -20 度
顶部

- 图像绕 z 轴旋转 20 度
- 裁剪图像并仅拍摄上部❀图像绕 z 轴 0 度x轴(为什么?为了和其他进程统一,也为了方便写代码的处理)
- 图像绕z轴旋转-20度
拼接
这两部分图像得到流程二中特定帧的效果
实现流程二的动画
保持每一帧绕x轴旋转角度固定,通过改变左右旋转角度即可实现流程二的动画z 轴。
改进流程一(方便写代码)
流程后半部分
- 图像绕 z 轴旋转 0 度
- 裁剪图像并仅去除一半图像绕 z 轴旋转x轴一定量。角度
- 图像绕z轴旋转0度
不断改变x轴的旋转角度可以在图像下部产生动画效果。处理
处理顶部
- 图像旋转 绕 z 轴旋转 0 度
- 裁剪图像并仅拍摄顶部
- 将图像绕 z 轴旋转 0 度
处理三
处理三并过程类似,不再赘述。
整个动画具体参数
- 流程一:
- 上半部分:旋转角度均为0
- 下半部分:绕z轴旋转角度始终为0,x轴旋转角度为0从0到-45度移动到
- 过程2:
- 上:绕z轴旋转角度从0到270度变化,绕x轴旋转角度固定为0度。绕z轴旋转角度始终为270度,绕x轴旋转角度为0-shift到45度
- 下半部分:绕z轴旋转角度始终为270度,并且绕 x 轴旋转角度始终为 0 度
代码编写
首先定义一个枚举来标识动画当前要去的位置 流程
enum FlipAnimationSteps { animation_step_1, animation_step_2, animation_step_3 }
复制代码
设置动画参数并监控动画状态 ' 参见核心类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
布局,可以一起推断上下半部的变换结果(注:无法使用Solumn
布局),两个儿童
的变化是上下部分变化的结果。可以观察到,两个Transforms
与之前的变换过程一致(绕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来指定特定的裁剪区域。
作者:MarioFeng
链接:https://juejin.im/post/5d0b4e65e51d45105e0212d6
来源:掘金版权所有若为商业转载请联系作者授权。非商业转载请注明出处。
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。