主要参考:https://flutterchina.club/animations/ ,这里有很系统的讲解,要深入理解flutter的动画,务必顺着教程,从Animation、AnimationController、Tween、动画监听等看下来。
我们直接从封装比较方便的AnimatedWidget开始学习demo快速上手:
1、线性变化
//放大动画的封装组件
class Animation1 extends StatefulWidget {
_Animation1State createState() => new _Animation1State();
}
class _Animation1State extends State<Animation1>
with SingleTickerProviderStateMixin {
AnimationController controller; //动画控制器,设置动画运行时间和值的计算规则
Animation<double> animation; //动画值,须传递给动画图标,动画之所以动就是因为该值在变化
initState() {
super.initState();
//必须传入动画时间和vsync,存在vsync时会防止屏幕外动画消耗不必要的资源
controller = new AnimationController(
duration: const Duration(milliseconds: 2000), vsync: this);
//animation值取0~300之间,通过Tween来映射生成
animation = new Tween(begin: 0.0, end: 300.0).animate(controller);
//启动动画
controller.forward();
}
Widget build(BuildContext context) {
//把动画值传入动画图标EnlargeLogo,当动画启动后,该图标就会根据动画值的变化而变化
return new EnlargeLogo(animation: animation);
}
dispose() {
//组件销毁时记得销毁动画
controller.dispose();
super.dispose();
}
}
//放大动画图标,本质就是一张图片,通过动画控制器修改height和width属性来达到放大的动画
class EnlargeLogo extends AnimatedWidget {
EnlargeLogo({Key key, Animation<double> animation})
: super(key: key, listenable: animation);
Widget build(BuildContext context) {
//传入的动画值
final Animation<double> animation = listenable;
return new Center(
child: new Container(
margin: new EdgeInsets.symmetric(vertical: 10.0),
height: animation.value,
width: animation.value,
child: new FlutterLogo(), //FlutterLogo
),
);
}
}
上面这段代码实现了一个图标在2s内逐步放大的过程,重要代码都有注释。初学阶段我们从最底层的动画实现开始,自己去分解、实现动画。
动画分解开来就是一个图形的坐标值、颜色、透明度等在变化,如demo是一个放大动画,就是图标的height、width属性在变大,那我们就可以把动画分解成两步:
1、长宽值在指定时间内按规律地增大,这里不涉及任何UI,就是单纯的数值增大;
2、把这个值应用到图标的height、width上,值变化的时候图标就跟随变化(这里的图标必然是个StatefulWidget了)。
以上面的demo为例,Animation<double> animation
就是逐渐增大的值,它还可以是其他格式像Animation<Color>等,我们要把它传给图标EnlargeLogo,设为图标的长宽值。
AnimationController controller
是动画控制器,是一个特殊的Animation对象,在指定Duration中线性生成01.0的值,也可以手动设置lowerBound和upperBound,比如设为00.5。
new Tween(begin: 0.0, end: 300.0).animate(controller)
,Tween也可以设置计算边界begin和end,并把这种变化规律映射给Animation。
2、监听animation值及状态
更进一步的,我们可以对animation值进行监听,只要值发生变化就调用;也可以对status进行监听,在动画结束后反转动画,在反转结束后再播放放大动画。
animation = new Tween(begin: 0.0, end: 300.0).animate(controller)
//..是级联符号,用上个表达式的返回值继续调用
//添加对animation值的监听
..addListener(() {
print(animation.value);
})
//添加状态监听放大结束后反转,缩小结束后正放。
..addStatusListener((status) {
print(status);
if (status == AnimationStatus.completed) {
controller.reverse(); //翻转动画
} else if (status == AnimationStatus.dismissed) {
controller.forward(); //翻转动画
}
});
3、实现非线性的变化
CurvedAnimation类可以将动画过程定义为一个非线性曲线,Curves.fastOutSlowIn属性表示曲线为快速开始慢速结束的,使用如下:
//非线性曲线
CurvedAnimation curve =
new CurvedAnimation(parent: controller, curve: Curves.fastOutSlowIn);
//animation值取0~300之间,通过Tween来映射生成
animation = new Tween(begin: 0.0, end: 300.0).animate(curve)
就在controller外再包一层变为非线性的,把CurvedAnimation映射给animation就行。动画的启动翻转等还是通过controller来操作。
4、AnimatedBuilder
教程中还有AnimatedBuilder重构,为什么要用AnimatedBuilder呢?按我的理解就是进一步解耦,把动画(单纯指动作)和组件(例子中的logo图标)分离开来,让动画可以方便复用在其他组件上。
AnimatedBuilder是继承自AnimatedWidget,原代码中的EnlargeLogo也是AnimatedWidget,所以AnimatedBuilder其实就是重构替代了AnimatedWidget,把class EnlargeLogo代码块替换为如下代码即可,其他像animation、controller的计算、启动都无需修改,就完成了AnimatedBuilder的重构。
//固定的图标组件,不在组件内设置宽高等
class SecondLogo extends StatelessWidget {
Widget build(BuildContext context) {
return Container(
//图标充满父组件
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: FlutterLogo(), //FlutterLogo
);
}
}
//放大动画,把需要进行动画的组件传入child,专注于动画过程,与图标组件解耦,返回的是已经套用过动画的组件
class EnlargeLogo extends StatelessWidget {
EnlargeLogo2({this.child, this.animation});
final Widget child; //需要进行动画的组件
final Animation<double> animation;
Widget build(BuildContext context) {
return new Center(
child: new AnimatedBuilder(
animation: animation,
builder: (BuildContext context, Widget child) {
return new Container(
height: animation.value,
width: animation.value,
child: child,
);
},
child: child),
);
}
}
仔细看AnimatedBuilder的源码,其实就是一个内置了animation的AnimatedWidget,和我们一开始的实现过程很像,反而在图标组件外多套一层Builder,除非项目中有大量复用该动画的场景,否则没必要使用AnimatedBuilder,用AnimatedWidget更灵活容易理解。
5、并行动画
一般来说并行动画就是给它设置两套动画就行,但是AnimatedWidget的构造函数只接受一个Animation,那我们就只能在AnimatedWidget内部,去定义两套Tween,用不同的规则把同个Animation映射给对应的属性赋值。
//并行动画的父组件
class Animation2 extends StatefulWidget {
_Animation2State createState() => new _Animation2State();
}
class _Animation2State extends State<Animation2> with TickerProviderStateMixin {
AnimationController controller;
Animation<double> animation;
initState() {
super.initState();
controller = new AnimationController(
duration: const Duration(milliseconds: 2000), vsync: this);
animation = new CurvedAnimation(parent: controller, curve: Curves.bounceOut);
//实现动画循环播放
animation.addStatusListener((status) {
if (status == AnimationStatus.completed) {
controller.reverse();
} else if (status == AnimationStatus.dismissed) {
controller.forward();
}
});
controller.forward();
}
Widget build(BuildContext context) {
return new TwoAnimationWidget(animation: animation);
}
dispose() {
controller.dispose();
super.dispose();
}
}
//两个并行动画组件,Tween的绑定从父组件挪到子组件中,利用Tween.evaluate,设置不同的Tween把同个animation映射给不同属性
class TwoAnimationWidget extends AnimatedWidget {
//通过opacityAnimation.value来获取不透明度
static final _opacityTween = new Tween<double>(begin: 0.1, end: 1.0);
//通过sizeAnimation.value来获取大小
static final _sizeTween = new Tween<double>(begin: 0.0, end: 300.0);
TwoAnimationWidget({Key key, Animation<double> animation})
: super(key: key, listenable: animation);
Widget build(BuildContext context) {
final Animation<double> animation = listenable;
return new Center(
child: new Opacity(
opacity: _opacityTween.evaluate(animation),
child: new Container(
margin: new EdgeInsets.symmetric(vertical: 10.0),
height: _sizeTween.evaluate(animation),
width: _sizeTween.evaluate(animation),
child: new FlutterLogo(),
),
),
);
}
}
6、Transition类:SizeTransition、FadeTransition、ScaleTransition、AlignTransition、RotationTransition、SlideTransition...
去官方API搜索Transition会发现一大堆已经封装好的Transition,常见的缩放、渐隐渐现、旋转等都有,Transition也是继承自AnimatedWidget,就相当于一个父容器,在你需要实现动画的组件外包一层Transition组件,并传入需要的Animation对象即可,controller同上面代码:
/**
* Transition类
*/
class Animation3 extends StatefulWidget {
_Animation3State createState() => new _Animation3State();
}
class _Animation3State extends State<Animation3> with TickerProviderStateMixin {
AnimationController controller;
Animation<double> animation;
initState() {
super.initState();
controller = new AnimationController(
duration: const Duration(milliseconds: 2000), vsync: this);
animation = new CurvedAnimation(parent: controller, curve: Curves.bounceOut);
//实现动画循环播放
animation.addStatusListener((status) {
if (status == AnimationStatus.completed) {
controller.reverse();
} else if (status == AnimationStatus.dismissed) {
controller.forward();
}
});
controller.forward();
}
Widget build(BuildContext context) {
//包裹缩放动画
return ScaleTransition(
scale: animation,
//包裹旋转动画
child: RotationTransition(
turns: animation,
child: Center(
child: Text('ScaleTransition + RotationTransition'),
),
),
);
}
dispose() {
controller.dispose();
super.dispose();
}
}
7、其他
以上是根据中文教程的学习笔记,看到别人写的demo,感觉这种写法更舒服,学习 Flutter知识点: Animation
网友评论