Flutter动画 1 - 实现一个最简单的动画
简述
动画效果会对提升用户体验有着显著效果,一个漂亮的动画效果会让人眼前一亮,反正就是用这舒服,如果没有动画效果的加持,整个App会显的非常生涩难用.对于Flutter的动画,作为小白的我也是最近才刚刚接触,所以这里就说一下Futter中如何使用动画.
这一篇主要是来说如何在Flutter中实现一个最简单的动画,这里假设你已经对Flutter有一些基本的了解或者有一定的开发经验了.
构建基础
这里我们就以控制 Container组件中 margin 为示例.
首先我们先创建一个StatefulWidget有状态组件,同时需要混合 SingleTickerProviderStateMixin,至于 SingleTickerProviderStateMixin 是什么,后面我们会讲到,具体代码如下所示.
class FlutterAnimationWidget extends StatefulWidget {
@override
_FlutterAnimationWidgetState createState() => _FlutterAnimationWidgetState();
}
class _FlutterAnimationWidgetState extends State<FlutterAnimationWidget> with SingleTickerProviderStateMixin {
****
}
我们接着创建一个受控Container,放在一个纵向布局中,具体如下所示.关于触发按钮的布局这里就不给过叙述了,不是本文的重点.
Container(
width: 200,
height: 50,
color: Colors.orangeAccent,
margin: EdgeInsets.only(top: 0),
),
构建动画
我们声明动画控制器 _animationController 和margin的top状态属性 _marginTop ,改造Container让margin受控于 _marginTop 的值.具体代码如下所示.
double _marginTop;
AnimationController _animationController;
Container(
...
margin: EdgeInsets.only(top: _marginTop),
...
),
紧接着我们就需要实现 _animationController 了, 假设我们设定动画运行时间为 300 毫秒,top的移动范围为[0, 50],那么具体代码就如下所示.
_marginTop = 0;
_animationController = AnimationController(duration: Duration(milliseconds: 300), vsync: this)..addListener(() {
setState(() {
_marginTop = _animationController.value * 50.0;
});
});
这里就要对上述代码进行说明一下, 首先是 AnimationController 这个动画控制类,动画控制类中的只能在给定的时间内线性生成 0 → 1 的数值(默认区间),所以我们需要映射成我们想要的数值就需要使用到 value 这个属性映射成我们想要的值,这里就是简单映射成 0 → 50.
那么我们可不可以改动默认区间呢?当然是可以的,这就需要使用到 lowerBound 和 upperBound 了.那么上面的代码就可以如下示例,两者的效果是一样的.
_animationController = AnimationController(duration: Duration(milliseconds: 300), lowerBound: 0, upperBound: 50, vsync: this)..addListener(() {
setState(() {
_marginTop = _animationController.value;
});
});
那么 代码中 vsync 指向 this,又是什么意思呢?这个简单的来说就是使用Ticker(而不是Timer)来驱动动画会防止屏幕外动画(动画的UI不在当前屏幕时,如锁屏时)消耗不必要的资源.还记得文章一开始状态组件混合 SingleTickerProviderStateMixin 吗?就是这个作用.如果一个组件内存在多个动画控制器,则可以使用 TickerProviderStateMixin .
如下是摘抄 <<Flutter 实战>> 中的对Ticker一段说明.
Flutter应用在启动时都会绑定一个SchedulerBinding,通过SchedulerBinding可以给每一次屏幕刷新添加回调,而Ticker就是通过SchedulerBinding来添加屏幕刷新回调,这样一来,每次屏幕刷新都会调用TickerCallback。使用Ticker(而不是Timer)来驱动动画会防止屏幕外动画(动画的UI不在当前屏幕时,如锁屏时)消耗不必要的资源,因为Flutter中屏幕刷新时会通知到绑定的SchedulerBinding,而Ticker是受SchedulerBinding驱动的,由于锁屏后屏幕会停止刷新,所以Ticker就不会再触发。
对于 addListener()
方法,是给 AnimationController 添加监听回调,并且每一帧都会回调一次.这个我们在里面调用 setState()
方法就能实现对状态组件UI的重建了.
当然了,除了 addListener()
帧监听回调方法之外,还有 addStatusListener()
动画状态的监听,监听的值如下所示.
状态 | 说明 |
---|---|
AnimationStatus.dismissed | 动画从 controller.reverse() 反向执行 结束时会回调此方法 |
AnimationStatus.forward | 执行 controller.forward() 会回调此状态 |
AnimationStatus.reverse | 执行 controller.reverse() 会回调此状态 |
AnimationStatus.completed | 动画从 controller.forward() 正向执行 结束时会回调此方法 |
使用的话,示例如下所示.
_animationController.addStatusListener((status) {
if (status == AnimationStatus.completed) print("动画完成");
});
构建完成动画控制器,但是不要忘了最后需要进行释放操作.代码如下所示.
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
执行动画
指定动画比较简单.主要有如下几个方法.
方法 | 说明 |
---|---|
forward() | 正向开始执行动画 |
reverse() | 反向开始执行动画 |
reset() | 重置动画到初始状态 |
dispose() | 取消/停止动画 |
这里我们就只需要在触发时间中调用 forward()
就可以了.具体示例如下所示.
void startEasyAnimation() {
_animationController.forward();
}
示例代码
由于这一篇比较简单,所以我们就把示例代码贴到文章中了,需要的拿走.
class FlutterAnimationWidget extends StatefulWidget {
@override
_FlutterAnimationWidgetState createState() => _FlutterAnimationWidgetState();
}
class _FlutterAnimationWidgetState extends State<FlutterAnimationWidget> with TickerProviderStateMixin {
AnimationController _animationController;
double _marginTop;
@override
void initState() {
super.initState();
_marginTop = 0;
_animationController = AnimationController(duration: Duration(milliseconds: 300), lowerBound: 0, upperBound: 50, vsync: this)..addListener(() {
setState(() {
_marginTop = _animationController.value;
});
});
_animationController.addStatusListener((status) {
if (status == AnimationStatus.completed) print("动画完成");
});
}
void startEasyAnimation() {
_animationController.forward();
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
width: 200,
height: 50,
color: Colors.orangeAccent,
margin: EdgeInsets.only(top: _marginTop),
),
FlatButton(
onPressed: startEasyAnimation,
child: Text(
"点击执行最简单动画",
style: TextStyle(color: Colors.black38),
),
),
],
),
),
);
}
}
结语
OK,使用Flutter实现一个最简单的动画就说道这里了,接下来,骚栋会发布更多Flutter动画相关文章,欢迎关注评论,感谢大家了.