写点什么

Flutter 构建三维空间动画效果

作者:岛上码农
  • 2022 年 7 月 11 日
  • 本文字数:2528 字

    阅读完需:约 8 分钟

Flutter 构建三维空间动画效果

前言

上一篇我们介绍了 AnimationAnimationController 的使用,这是最基本的动画构建类。但是,如果我们想构建一个可复用的动画组件,通过外部参数来控制其动画效果的时候,上一篇的方法就不太合适了。在 Flutter 中提供了 AnimatedWidget 组件用于构建可复用的动画组件。本篇我们用 AnimatedWidget 来实现组件的 3D 旋转效果,如下图所示。


AnimatedWidget 简介

AnimatedWidget是一个抽象的 StatefulWidget, 构造方法如下所示。


const AnimatedWidget({    Key? key,    required this.listenable,  }) : assert(listenable != null),       super(key: key);
复制代码


主要在于接收一个 listenable 参数,通常会是 Animation 对象。在 AnimatedWidget 内部的_AnimatedState 类中,会添加该对象变化监听回调,进而刷新界面。


class _AnimatedState extends State<AnimatedWidget> {  @override  void initState() {    super.initState();    widget.listenable.addListener(_handleChange);  }
@override void didUpdateWidget(AnimatedWidget oldWidget) { super.didUpdateWidget(oldWidget); if (widget.listenable != oldWidget.listenable) { oldWidget.listenable.removeListener(_handleChange); widget.listenable.addListener(_handleChange); } }
@override void dispose() { widget.listenable.removeListener(_handleChange); super.dispose(); }
void _handleChange() { setState(() { // The listenable's state is our build state, and it changed already. }); } // ...}
复制代码


可以看到,只需要将 Animation 对象传给 AnimatedWidget 对象后,就不需要我们之前那样自己写 addListener 之类的处理了。而整个动画可以交给外部其他对象来控制,从而实现动画组件的复用。

3D 旋转动画的实现

3D 旋转的实现比较简单,在 Container 组件有两个参数控制转换(transform),分别是:


  • transformMatrix4对象,可以实现围绕 X、Y、Z 轴的旋转、平移,以及变形等效果。关于 Matrix4涉及到很多矩阵运算和线性代数的知识,可以参考 Matrix4 的源码自行温习一下大学的数学知识😂。

  • transformAlignment:转换的对齐方式,可以理解为起点位置,可以使用 Alignment 对象来设置。


有了这个基础,我们就可以定义 3D 旋转动效了,我们定义一个通用的组件,ThreeDAnimatedWidget


class ThreeDAnimatedWidget extends AnimatedWidget {  final Widget child;  const ThreeDAnimatedWidget(      {Key? key, required Animation<double> animation, required this.child})      : super(key: key, listenable: animation);
@override Widget build(BuildContext context) { final animation = listenable as Animation<double>;
return Center( child: Container( transform: Matrix4.identity() ..rotateY(2 * pi * animation.value) ..setEntry(1, 0, 0.01), transformAlignment: Alignment.center, child: child, ), ); }}
复制代码


这里我们设置的是围绕中心点绕 Y 轴旋转,并使用 setEntry 设置了一定的倾斜角 (这会更有立体感)。实际我们也可以设置围绕 X 轴或 Z 轴旋转。接下来就是这个动画组件的应用了,我们构建一个带有阴影的文字(看起来像立体字)作为这个动画的子组件,其他的控制和上一篇的是类似的,完整代码如下:


class AnimatedWidgetDemo extends StatefulWidget {  const AnimatedWidgetDemo({Key? key}) : super(key: key);
@override _AnimatedWidgetDemoState createState() => _AnimatedWidgetDemoState();}
class _AnimatedWidgetDemoState extends State<AnimatedWidgetDemo> with SingleTickerProviderStateMixin { late Animation<double> animation; late AnimationController controller;
@override void initState() { super.initState(); controller = AnimationController(duration: const Duration(seconds: 3), vsync: this); animation = Tween<double>(begin: 0.0, end: 1.0).animate(controller); }
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('AnimatedWidget 动画'), ), body: ThreeDAnimatedWidget( animation: animation, child: Text( '岛上码农', style: TextStyle( fontSize: 42.0, color: Colors.blue, fontWeight: FontWeight.bold, shadows: [ Shadow( blurRadius: 2, offset: Offset(2.0, 1.0), color: Colors.blue[900]!), ], ), ), ), floatingActionButton: FloatingActionButton( child: Icon(Icons.play_arrow, color: Colors.white), onPressed: () { if (controller.status == AnimationStatus.completed) { controller.reverse(); } else { controller.forward(); } }, ), ); }
@override void dispose() { controller.dispose(); super.dispose(); }}
复制代码


可以看到,这个 ThreeDAnimatedWidget 可以做到复用了,在需要这样动效的场景里,按照上面的方式给它传入 Animation 对象和子组件就可以了。例如我们将文字修改为一张图片。


//...body: ThreeDAnimatedWidget(  animation: animation,  child: Image.asset(    'images/avatar.jpg',    width: 100,    height: 100,  ),),//...
复制代码


总结

本篇介绍了 AnimatedWidget 的使用,通过 AnimatedWidget 可以构建可复用的动画组件。同时,还通过 Containertransform 属性加上 AnimatedWidget 实现了三维空间的旋转效果。实际开发过程中,我们可以基于 AnimatedWidget 构建很多个性化的、可复用的动画组件,提升应用的趣味性。


发布于: 刚刚阅读数: 3
用户头像

岛上码农

关注

用代码连接孤岛,公众号@岛上码农 2022.03.03 加入

从南漂到北,从北漂到南的业余码农

评论

发布
暂无评论
Flutter 构建三维空间动画效果_flutter_岛上码农_InfoQ写作社区