前言 :
在Flutter 中,做动画离不开这么一个类,那就是
Animation
这个类如往常一样,也是一个抽象类。
abstract class Animation<T> extends Listenable implements ValueListenable<T>
整个animation.dart 文件只有两百多行代码,其中包含了大量的注释。
这句话应该就可以看出Tween 的重要性了。
一 Animation
了解一下这个类中的方法吧。
1 addListener
每当动画的值改变的时候,动画就会通知所有通过addListener 添加的监听器
2 addStatusListener
简而言之,就是动画的状态发生改变的时候,会通知所有通过addStatusListener 添加的监听器 。
-
当动画的状态发生变化时,会通知所有通过
addStatusListener
添加的监听器。 -
通常情况下,动画会从
dismissed
状态开始,表示它处于变化区间的开始点。 -
举例来说,从 0.0 到 1.0 的动画在
dismissed
状态时的值应该是 0.0。 -
动画进行的下一状态可能是
forward
(比如从 0.0 到 1.0)或者reverse
(比如从 1.0 到 0.0)。 -
最终,如果动画到达其区间的结束点(比如 1.0),则动画会变成
completed
状态。
3 removeListener
4 removeStatusListener
二 AnimationController
AnimationController 这个是继承Anamation的,里面有几个属性比较重要
值得注意的是 vsync 参数是必传的,Flutter的渲染闭环,每次渲染一帧画面之前都需要一个vsync信号,所以需要将SingleTickerProviderStateMixin 混入到State 类中才能初始化AnimationController
AnimationController({
double? value,
this.duration,
this.reverseDuration,
this.debugLabel,
this.lowerBound = 0.0,
this.upperBound = 1.0,
this.animationBehavior = AnimationBehavior.normal,
required TickerProvider vsync,
}) : assert(lowerBound != null),
assert(upperBound != null),
assert(upperBound >= lowerBound),
assert(vsync != null),
_direction = _AnimationDirection.forward {
_ticker = vsync.createTicker(_tick);
_internalSetValue(value ?? lowerBound);
}
三 CurvedAnimation
这个类可以将AnimationController 和 Curve 结合起来,生成一个新的Animation 对象,有什么用呢?这个地方可以设置动画的速率。
它也是继承与Animation
CurvedAnimation({
required this.parent,
required this.curve,
this.reverseCurve,
}) : assert(parent != null),
assert(curve != null) {
_updateCurveDirection(parent.status);
parent.addStatusListener(_updateCurveDirection);
}
这个Curve 类型的对象有一些常量,可以直接调用
四 Tween
Tween 动画,又称为补间动画,有两个关键参数,begin 和 end
这玩意有什么用呢?
它主要是弥补 AnimationController 动画值只能为 double 类型的不足,,所以需要不同类型的变化值,那么就可以使用 Tween 。
类型多种多样
ColorTween ,TextStyleTween,DecorationTween
_colorAnimation =
ColorTween(begin: primaryColor[100], end: primaryColor[900])
.animate(_animationController!)
..addListener(() {
setState(() {});
})
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
_animationController!.reverse();
} else if (status == AnimationStatus.dismissed) {
_animationController!.forward();
}
});
//字体样式动画
_textStyleAnimation = TextStyleTween(
begin: TextStyle(color: Colors.orange, fontSize: 15),
end: TextStyle(color: Colors.redAccent, fontSize: 30))
.animate(_animationController!)
..addListener(() {
setState(() {});
});
_sizeAnimation =
Tween(begin: Size(50, 50), end: Size(200, 200)).animate(_controller!)
..addListener(() {
setState(() {});
})
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller!.reverse();
}
});
_demo2() {
return Container(
width: _sizeAnimation!.value.width,
height: _sizeAnimation!.value.height,
color: Colors.blueAccent,
);
}
五 使用
上面把该介绍的都介绍了,那么如何使用呢 ?
就是如下方式使用
import 'package:flutter/material.dart';
class StudyAnimationPage extends StatefulWidget {
const StudyAnimationPage({super.key});
@override
State<StudyAnimationPage> createState() => _StudyAnimationPageState();
}
class _StudyAnimationPageState extends State<StudyAnimationPage>
with SingleTickerProviderStateMixin {
// 控制器
AnimationController? _controller;
CurvedAnimation? _curvedAnimation;
// 动画
Animation? _colorAnimation;
// size 动画
Animation<Size>? _sizeAnimation;
@override
void initState() {
// TODO: implement initState
super.initState();
// 这个this 必须在方法里面写才不报错
//1 初始化控制器
_controller =
AnimationController(vsync: this, duration: Duration(seconds: 2));
// 2 这里可以设置执行动画的速率
_curvedAnimation =
CurvedAnimation(parent: _controller!, curve: Curves.easeInOutCirc);
// 3 动画
_colorAnimation =
ColorTween(begin: Colors.redAccent, end: Colors.blueAccent)
.animate(_controller!)
..addListener(() {
setState(() {});
})
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller!.reverse();
}
});
_sizeAnimation =
Tween(begin: Size(50, 50), end: Size(200, 200)).animate(_controller!)
..addListener(() {
setState(() {});
})
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller!.reverse();
}
});
}
@override
Widget build(BuildContext context) {
print("build方法被调用了哈哈哈");
return Scaffold(
appBar: AppBar(
title: Text("data"),
),
body: Container(
width: double.infinity,
height: double.infinity,
child: Column(
children: [_demo1(), _demo2()],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_controller!.forward();
},
child: Icon(Icons.open_in_browser),
),
);
}
_demo1() {
return Container(
color: _colorAnimation!.value,
width: 100,
height: 100,
);
}
_demo2() {
return Container(
width: _sizeAnimation!.value.width,
height: _sizeAnimation!.value.height,
color: Colors.blueAccent,
);
}
}
以上的代码还有一个问题,因为监听动画的值不停的改变,那么setState就会不停的执行,略显繁重,所以就可以 使用AnimatedWidget
六 AnimatedWidget
创建一个Widget 继承与AnimatedWidget ,然后Widget 的build 就不会随着动画值的改变一直执行,而是执行我们自己创建的Widget 中的build
class MyAnimatedBox extends AnimatedWidget {
// 这里传递 一个animation 过来 传递给父类
MyAnimatedBox(Animation anim) : super(listenable: anim);
@override
Widget build(BuildContext context) {
Animation sizeAnimation = listenable as Animation;
// TODO: implement build
return Container(
width: sizeAnimation.value.width,
height: sizeAnimation.value.height,
color: Colors.blueAccent,
);
}
}
调用
_demo2() {
return MyAnimatedBox(_sizeAnimation!);
}
当然还有一些问题:
1 每一次使用都要创建一个类,这有点麻烦
2 如果我们构建的Widget 有子类,那么子类依然会重复的build
so 怎么解决呢 ??
七 AnimatedBuilder
1 不用显式的去添加帧监听器,然后再调用setState() 了,这个好处和AnimatedWidget是一样的。
2 更好的性能:因为动画每一帧需要构建的 widget 的范围缩小了,如果没有builder,setState()将会在父组件上下文中调用,这将会导致父组件的build方法重新调用;而有了builder之后,只会导致动画widget自身的build重新调用,避免不必要的rebuild。
_demo3() {
return AnimatedBuilder(
animation: _sizeAnimation!,
// child: child,
builder: (BuildContext context, Widget? child) {
return Container(
width: _sizeAnimation!.value.width,
height: _sizeAnimation!.value.height,
color: Colors.blueAccent,
);
},
);
}
八 交织动画
什么事交织动画,顾名思义,就是多个动画交织在一起。通过多个Tween 生成多个Animation 对象。
// 动画
Animation? _colorAnimation;
// size 动画
Animation<Size>? _sizeAnimation;
// 透明度动画
Animation<double>? _opacityAnimation;
// 旋转动画
Animation<double>? _rotationAnim;
// 3 动画
_colorAnimation =
ColorTween(begin: Colors.redAccent, end: Colors.blueAccent)
.animate(_controller!)
..addListener(() {
setState(() {});
});
// ..addStatusListener((status) {
// if (status == AnimationStatus.completed) {
// _controller!.reverse();
// }
// });
_sizeAnimation =
Tween(begin: Size(50, 50), end: Size(200, 200)).animate(_controller!);
_opacityAnimation = Tween(begin: 0.3, end: 1.0).animate(_controller!);
_rotationAnim = Tween(begin: 0.0, end: 2 * pi).animate(_controller!);
/**
* 多个动画交织在一起
* 1 大小变化的动画
* 2 颜色变化的动画
* 3 透明度变化的动画
* 4 旋转的动画
*
*/
_demo4() {
return Opacity(
opacity: _opacityAnimation!.value,
child: Transform(
alignment: Alignment.center,
transform: Matrix4.rotationZ(_rotationAnim!.value),
child: Container(
color: _colorAnimation!.value,
width: _sizeAnimation!.value.width,
height: _sizeAnimation!.value.height,
),
),
);
}