Flutter 学习之旅(三十五) Flutter 动画(二)
上一篇我们写了一个文件渐变动画,但是写法非常繁琐,而且动画和控件结合的太紧密了,如果继续添加别的效果需要修改的地方太多了,先把上一篇最后的代码贴出来,我们一点一点优化
class _TsmAnimationState extends State<TsmAnimationPage>
with SingleTickerProviderStateMixin {
Animation<int> animation;
AnimationController animatedContainer;
@override
void initState() {
animatedContainer = AnimationController(
duration: Duration(seconds: 2),
lowerBound: 0,
upperBound: 1,
vsync: this);
///添加变速模型
Animation curve =
CurvedAnimation(parent: animatedContainer, curve: Curves.easeIn,);
///改变起始值
animation = IntTween(begin: 0, end: 255).animate(curve);
animatedContainer.addListener(() {
setState(() {
printString("value:${animation.value}");
});
});
animatedContainer.addStatusListener((status) {
if(status==AnimationStatus.completed){
animatedContainer.reverse();
}else if(status==AnimationStatus.dismissed){
animatedContainer.forward();
}
});
animatedContainer.forward();
super.initState();
}
@override
void dispose() {
animatedContainer.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Animation 学习"),
centerTitle: true,
),
body: Container(
child: Center(
child: Text(
"重复渐隐动画",
style: TextStyle(
color: Color.fromARGB(animation.value, 1, 1, 1),
),
),
),
),
);
}
}
这里先梳理一下创建动画的过程,
1 创建AnimationController 指定动画时间 ,和 TickerProvider
2 创建Animation 指定AnimationController 和 动画速度变化器curve
我们执行动画的过程中可能同时执行多个动画,但是每个动画执行的时机可能不同,这就要求我们要控制每个动画开始的时间和结束的时间,
CurvedAnimation(parent: _controller,curve: Interval(0.2,0.8,curve: Curves.easeOut))
Interval 两个必要参数,开始和结束时间 还有一个非必要参数速度变化器,这样就能指定这个动画执行的时机
3 创建Tween 利用Tween改变Animation 的值,可以是color offset rect double int 等等
4 添加监听,在数据发生变化时利用setState 方法重新绘制widget,
整个过程中我们发现第4步其实是通用的,而且每次添加监听在关闭页面的时候还需要移除,如果每次定义动画都这么写的话太繁琐,而且移除部分还会忘记,其实可以精简一下
这里就引出了 AnimatedWidget
AnimatedWidget
先来看一下AnimatedWidget 类
abstract class AnimatedWidget extends StatefulWidget {
/// Creates a widget that rebuilds when the given listenable changes.
///
/// The [listenable] argument is required.
const AnimatedWidget({
Key key,
@required this.listenable,
}) : assert(listenable != null),
super(key: key);
/// The [Listenable] to which this widget is listening.
///
/// Commonly an [Animation] or a [ChangeNotifier].
final Listenable listenable;
/// Override this method to build widgets that depend on the state of the
/// listenable (e.g., the current value of the animation).
@protected
Widget build(BuildContext context);
/// Subclasses typically do not override this method.
@override
_AnimatedState createState() => _AnimatedState();
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<Listenable>('animation', listenable));
}
}
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.
});
}
@override
Widget build(BuildContext context) => widget.build(context);
}
入参需要一个Listenable ,而Animation 就是继承自 Listenable,这里给一个animation就可以了,
其他的放在state ,在initState 方法中添加监听,在 didUpdateWidget 移除原有监听,并添加新的监听,dispose中移除监听
例子
class TsmAnimatedBuilderPage extends StatefulWidget {
@override
State<StatefulWidget> createState() => _TsmAnimatedBuilderState();
}
class TextAnimatedWidget extends AnimatedWidget {
TextAnimatedWidget({
Key key,
@required Animation<int> animation,
}) : super(key: key, listenable: animation);
@override
Widget build(BuildContext context) {
Animation<int> animation = listenable;
printString(animation.value);
return Text(
"上面图片Hero动画",
style: TextStyle(
color: Color.fromARGB(animation.value, 1, 1, 1),
),
);
}
}
class _TsmAnimatedBuilderState extends State<TsmAnimatedBuilderPage> with SingleTickerProviderStateMixin{
AnimationController _controller;
Animation<int> _animation;
@override
void initState() {
_controller=AnimationController(duration: Duration(seconds: 1,),vsync: this);
_animation=IntTween(begin: 0,end: 255,)
.animate(CurvedAnimation(parent: _controller,curve: Interval(0.2,0.8,curve: Curves.easeOut)));
_controller.addStatusListener((status) {
if(_controller.status==AnimationStatus.completed){
_controller.reverse();
}else if(_controller.status==AnimationStatus.dismissed){
_controller.forward();
}
});
_controller.forward();
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Animation 学习"),
centerTitle: true,
),
body: Container(
child: Center(
child: TextAnimatedWidget(animation: _animation,),
),
),
);
}
}
这里我们发现使用AnimatedWidget 可以把widget从动画里面分离出来,但是给widget 设置动画属性还是必须写在widget 里面,这样不是很友好,而且动画如果这么写有一个弊端,就是整个widget 可能部分需要动画,一部分不需要动画,那么不需要动画的部分也会跟着重建,这样不是很友好,
AnimatedBuilder
AnimatedBuilder 继承自 AnimatedWidget 他增加了一个build 方法,
源码对他的介绍是这样的,如果子树的返回值不是跟着动画而改变的,不用每次随着动画的刷新更新child,
这样的做法会更大限度的提升动画的流畅性
先来看一下类
class AnimatedBuilder extends AnimatedWidget {
const AnimatedBuilder({
Key key,
@required Listenable animation,
@required this.builder,
this.child,
}) : assert(animation != null),
assert(builder != null),
super(key: key, listenable: animation);
/// Called every time the animation changes value.
final TransitionBuilder builder;
/// The child widget to pass to the [builder].
///
/// If a [builder] callback's return value contains a subtree that does not
/// depend on the animation, it's more efficient to build that subtree once
/// instead of rebuilding it on every animation tick.
///
/// If the pre-built subtree is passed as the [child] parameter, the
/// [AnimatedBuilder] will pass it back to the [builder] function so that it
/// can be incorporated into the build.
///
/// Using this pre-built child is entirely optional, but can improve
/// performance significantly in some cases and is therefore a good practice.
final Widget child;
@override
Widget build(BuildContext context) {
return builder(context, child);
}
}
继承自 AnimatedWidget ,就是对有不需要变化的子widget做了一层封装,这样更流畅
例子
class _TsmAnimatedBuilderState extends State<TsmAnimatedBuilderPage> with SingleTickerProviderStateMixin{
AnimationController _controller;
Animation<int> _animation;
@override
void initState() {
_controller=AnimationController(duration: Duration(seconds: 1,),vsync: this);
_animation=IntTween(begin: 0,end: 255,)
.animate(CurvedAnimation(parent: _controller,curve: Interval(0.2,0.8,curve: Curves.easeOut)));
_controller.addStatusListener((status) {
if(_controller.status==AnimationStatus.completed){
_controller.reverse();
}else if(_controller.status==AnimationStatus.dismissed){
_controller.forward();
}
});
_controller.forward();
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Animation 学习"),
centerTitle: true,
),
body: AnimatedBuilder(
animation: _animation,
builder: (context,child){
return Container(
child: child,
color: Color.fromARGB(_animation.value, 255, 1, 1),
);
},
child: Center(
child: Text('渐变动画'),
),
),
);
}
}
效果图
GIF 2020-9-15 15-33-41.gif
这次是改变背景色,要是改变自己颜色和使用animatedWidget没啥区别了,
我学习flutter的整个过程都记录在里面了
https://www.jianshu.com/c/36554cb4c804
最后附上demo 地址