Flutter动画系列-2
显式动画
显式动画需要借助动画控制器AnimaterController实现动画效果,可以随时开启、停止、循环播放,或者多个控件一起完成一个移动、缩放等效果,实现多种动画效果,这类控件一般命名为xxxTransition,如RotationTransition(旋转)、SlideTransition(位移)、FadeTransition(淡入淡出)等,同样如果自带控件不能满足你的需求,还可以使用AnimatedBuilder完成自己想要的动画,如果隐式动画和显示动画都没能满足你的需求,还可以直接使用CustomPainter使用Canvan绘制完成自己想要的效果
1、RotationTransition
先看一个简单的旋转动画
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('显式动画'),
),
body: Center(
child: RotationTransition(
turns: _controller,
child: Container(
width: 200,
height: 150,
color: Colors.orange,
)
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: (){
_controller.forward();
},
),
);
}
我们发现RotationTrasition让我们传一个turns参数,以前我们在介绍AnimatedContainer等简单动画的时候让我们传的是duration动画时间,现在这个turns 是一个什么呢,我在代码里也已经写了,传的是一个controller,就是AnimationController的实例,duration是在这个实例初始化的时候传入,当然也可以在后续需要的时候传入或修改
在代码里还可以看到我们传了一个
vsync:this
而且我们还混入了一个类:SingleTickerProviderStateMixin
,这个vsync是垂直信号同步,进入到SingleTickerProviderStateMixin源码中
我们看到Flutter帮我们创建了一个Ticker,从字面意思来解释ticker是钟表,我们知道有些设备是60Hz,有些是90Hz,60Hz的设备是每一秒渲染60次,ticker是每次渲染的回调,controller绑定一个ticker,所以controller也会在设备每一帧回调时去刷新控件,然后controller会为我们在0~1(默认是0~1)之间填充一些值,在builder中我们就可以拿到这些变化的值,完成动画渲染的区间,我们可以添加一个监听来打印controller的值
_controller.addListener(() { print(_controller.value); });
ticker还为我们做了一些优化,例如在动画的过程中,我们按home回到设备主屏幕,这时候动画就停止了,当我们再次回到页面的时候动画又重新开始了。需要注意的是controller需要在页面销毁的时候回收,也就是在dispose方法中调用controller的dispose,让动画动起来就调用controller的forward()方法,如果想让动画一直旋转,就调用repeat()方法,reset是重置,回归到最开始的位置,stop是直接停止。
刚才说controller默认动画是从0到1,我们想让他转1/4圈怎么办?有2个属性可以设置lowerBound和upperBound:
_controller = AnimationController(
lowerBound: 0,
upperBound: 0.25,
duration: Duration(seconds: 1),
vsync: this
);
如果我们的页面里定义了多个controller,就不能用SingleTickerProviderStateMixin了,需要使用TickerProviderStateMixin,在这种情况下可能运行会慢一些了
2、FadeTransition
FadeTransition淡入淡出跟上面旋转用法基本一样,只不过它现在要传一个opacity不透明度属性,
FadeTransition(
opacity: _controller,
child: Container(
width: 200,
height: 150,
color: Colors.orange,
)
)
3、 ScaleTransition
同理移动动画SlideTransition缩放动画需要传一个position属性,这时候我们发现直接给SlideTransition传它需要的position不行了,因为我们上面说过controller的默认值lowerBound为0,upperBound为1,是double类型,而这里需要的是Animate<Offset>类型,其实上面RotationTransition的turns属性还有一种写法:
turns: Tween(begin: 0.0, end: 1.0).animate(_controller),
或者:
turns: _controller.drive(Tween(begin: 0.0, end: 1.0)),
好像这两种的第一种在网上更为常见,所以移动动画的position 在这里可以这样写:
SlideTransition(
position: Tween(begin: Offset(0, 0), end: Offset(0, 0.5)).animate(_controller),
child: Container(
width: 200,
height: 150,
color: Colors.orange,
)
)
这里Tween end值传的是Offset(0, 0.5) 这个0.5是自身高度的0.5倍,需要注意一下而不是0.5个单位长度
刚才说了turns: Tween(begin: 0.0, end: 1.0).animate(_controller)
这种写法在网上比较常见,因为Tween还可以叠加:
SlideTransition(
position: Tween(begin: Offset(0, 0), end: Offset(0, 1))
.chain(CurveTween(curve: Curves.easeIn))
.animate(_controller),
child: Container(
width: 200,
height: 150,
color: Colors.orange,
)
)
curve设置可以设置一个区间:Interval(0, 0.5)意思是从0到0.5倍给定的时间来执行动画,后面0.5到1.0倍的时间处于空闲状态。 这个Interval都在什么情况下用呢?接下来看一个小例子:
double _interval = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('显式动画'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(5, (index) {
_interval+= 0.2;
return MoveBox(controller: _controller, color: Colors.orange[100 * (index + 1)], interval: Interval(_interval - 0.2, _interval),);
}),
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: (){
_controller.repeat(reverse: true);
// _controller.forward();
},
),
);
}
class MoveBox extends StatelessWidget {
const MoveBox({
Key key,
@required AnimationController controller,
this.color,
this.interval,
}) : _controller = controller, super(key: key);
final AnimationController _controller;
final Color color;
final Interval interval;
@override
Widget build(BuildContext context) {
return SlideTransition(
position: Tween(begin: Offset(0, 0), end: Offset(0.1, 0))
.chain(CurveTween(curve: Curves.bounceOut))
.chain(CurveTween(curve: interval))
.animate(_controller),
child: Container(
width: 300,
height: 80,
color: color,
)
);
}
}
上面就用到了Interval来控制动画什么时候开始。
在上一篇隐式动画提到如果想自定义动画而不想用Flutter自带的控件可以使用TweenAnimationBuilder,显示动画也可以使用AnimatedBuilder自定义动画:
body: Center(
child: AnimatedBuilder(
animation: _controller,
builder: (BuildContext context, Widget child) {
return Opacity(
opacity: _controller.value,
child: Container(width: 150, height: 150, color: Colors.orange),
);
}
),
),
得到一个闪烁的动画
上面这种写法opacity: _controller.value,
总感觉好像不是很妥当,因为通过前面的文章我们都习惯了用Tween来控制区间,其实我们更常用的写法是下面这种:
opacity: Tween(begin: 0.0, end: 1.0).animate(_controller).value
又见到了我们熟悉的Tween,这样我们就又可以嵌套多种效果了:
最后再来详细说一下Ticker,初始化controller的时候需要混入一个ticker类,并且vsync:this,来完成我们的动画,我们也可以不用绑定ticker,自己可以实现一个ticker完成动画效果:
@override
void initState() {
Ticker ticker = Ticker((_){
setState(() {
_height+=5;
if (_height > 300) _height = 0;
});
});
ticker.start();
super.initState();
}
body: Center(
child: Container(
width: 200,
height: _height,
color: Colors.orange,
),
)
_height 初始值为0
但是,Flutter不建议我们这样直接操作Ticker,所以才为我们创建了一个混入类,并且为我们做了很多优化,在60Hz分辨率的设备上这个动画执行时间是1秒钟,在120Hz分辨率的设备上0.5秒动画效果就执行完了,所以还是用系统写好的ticker吧。
转载自:https://juejin.cn/post/6949821554037358605