官方文档
必要的概念和类
-
Animation
对象,是Flutter
动画库中的核心类,插入用于引导动画的值 -
Animation
对象知道当前动画的状态(如:动画是否开始,停止,前进或者后退),但对屏幕上显示的内容一无所知 -
AnimationController
对象管理着Animation
-
CurvedAnimation
将动画定义成非线性运动的动画 -
Tween
在被动画对象使用的数据范围之间进行插值。例如,Tween
可能会定义从红色到蓝色或从 0 到 255 的插值 - 使用
Listeners
和StatusListeners
来监听动画状态的变化
步骤
-
初始化一个
AnimationController
对象AnimationController controller = AnimationController(duration: const Duration(milliseconds: 500), vsync: this);
-
初始化一个
Animation
对象, 并将AnimationController
作为参数传递进去。这里的Animation
对象是通过Tween
对象的animate
方法创建的Animation
alpha = IntTween(begin: 0, end: 255).animate(controller); -
调用
AnimationController
的forward()
方法执行动画controller.forward();
-
在
Widget
的dispose()
方法中调用释放资源controller.dispose();
案例
下面的案例是在给定的时间内改变 widget 的宽高
- 我们需要实时获取
Animation
的value
来赋给 widget 的宽高 - 要改变 widget 的宽高,那么就需要
setState(...)
来让 widget 重绘
首先我们需要一个可以改变状态的 widget
,也就是继承自 StatefulWidget
,然后按照我们上述的步骤来,代码如下:
class AnimateApp extends StatefulWidget {
@override
State createState() {
return _AnimateAppState();
}
}
class _AnimateAppState extends State with SingleTickerProviderStateMixin {
AnimationController controller;
Animation animation;
@override
void initState() {
super.initState();
// 创建 AnimationController 对象
controller = AnimationController(
vsync: this, duration: const Duration(milliseconds: 2000));
// 通过 Tween 对象 创建 Animation 对象
animation = Tween(begin: 50.0, end: 200.0).animate(controller)
..addListener(() {
// 注意:这句不能少,否则 widget 不会重绘,也就看不到动画效果
setState(() {});
})
// 执行动画
controller.forward();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'AnimateApp',
theme: ThemeData(
primaryColor: Colors.redAccent
),
home: Scaffold(
appBar: AppBar(
title: Text('AnimateApp'),
),
body: Center(
child: Container(
// 获取动画的值赋给 widget 的宽高
width: animation.value,
height: animation.value,
decoration: BoxDecoration(
color: Colors.redAccent
),
),
)
)
);
}
@override
void dispose() {
// 资源释放
controller.dispose();
super.dispose();
}
}
效果如下:
可以看出上面的动画是线性运动的,我们可以通过 CurvedAnimation
来实现非线性运动的动画代码如下:
controller = AnimationController(
vsync: this, duration: const Duration(milliseconds: 2000));
// 非线性动画
final CurvedAnimation curve = CurvedAnimation(
parent: controller, curve: Curves.elasticOut);
animation = Tween(begin: 50.0, end: 200.0).animate(curve)
..addListener(() {
setState(() {});
});
效果如下:
然后我们还可以给动画添加状态监听,通过给 Animation
添加 addStatusListener(...)
来监听当前动画的状态,如:动画是否播放完成。我们可以给上面的例子加一个状态监听,让动画无限执行:
animation = Tween(begin: 50.0, end: 200.0).animate(curve)
..addListener(() {
setState(() {});
})
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
controller.reverse();
} else if (status == AnimationStatus.dismissed) {
controller.forward();
}
});
AnimationStatus.completed
表示动画在结束时停止的状态,这个时候我们让动画反向执行(从后往前);AnimationStatus.dismissed
表示动画在开始时就停止的状态,这个时候我们让动画正常执行(从前往后)。这样就可以让动画无限执行了。
Tween
还可以接受 Color
类型的参数,实现颜色渐变的效果,下面让 widget 的颜色从 红色 渐变到 蓝色,部分代码如下:
controller = AnimationController(
duration: const Duration(milliseconds: 3000), vsync: this);
animation = ColorTween(begin: Colors.redAccent, end: Colors.blue).animate(
controller)
..addListener(() {
setState(() {});
});
controller.forward();
...
child: Container(
decoration: BoxDecoration(
color: animation.value
),
margin: EdgeInsets.symmetric(vertical: 10.0),
height: 200.0,
width: 200.0,
),
效果如下:
小提示
with
我们可以看到 _AnimateAppState
类后面跟了一个 with SingleTickerProviderStateMixin
,这个 with
什么意思呢 ? with
是 Dart 中的一个关键字,可以把它理解为 混入(mixin) 。可以看 stackoverflow 上的这个回答。
混入(mixin) 指的是可以将一个或多个类的功能添加到自己的类中,而无需继承这些类。混入后可以调用这些类中的方法。Dart 中没有多继承,可以使用 混入(mixin)来避免多重继承会导致的问题。
上述代码中 _AnimateAppState
类 继承了 State
类,但是初始化 AnimationController
的时候需要一个 TickerProvider
类型的 vsync
参数,所以我们 混入了 TickerProvider
的子类 SingleTickerProviderStateMixin
看一个简单的混入的小例子
void main() {
var a = new A();
a.methodB();
a.methodC();
a.methodD();
new E(a);
}
class A extends B with C, D {
}
class B {
void methodB() {
print('Class B methodB is call');
}
}
class C {
void methodC() {
print('Class C methodC is call');
}
}
class D {
void methodD() {
print('Class D methodD is call');
}
}
class E {
final C c;
E(this.c) {
c.methodC();
}
}
可以看出类 A
继承了类 B
,然后 混入 了类 C
和类 D
,然后在 main
方法中可以使用类 A
的实例去调用 类 B
, C
, D
中的方法。然后还有类 E
,构造方法中需要一个类 C
作为参数,然后因为类 A
混入了类 C
,所以可以把类 A
的实例当作参数传入到类 E
的构造方法中。
运行输出如下:
Class B methodB is call
Class C methodC is call
Class D methodD is call
Class C methodC is call
..addListener
在上面的例子中,我们看到了这样的写法
animation = Tween(begin: 50.0, end: 200.0).animate(curve)
..addListener(() {
setState(() {});
});
注意到 addListener
前面的两个点号 ..
了吧,什么意思呢?直接看个小例子吧!
void main() {
List list = getList()
..add("android")
..add("flutter")
..add("kotlin")
..removeAt(0);
list.forEach((item) {
print(item);
});
// ----------等同于
print('---------------------------');
List list2 = getList();
list2.add("android");
list2.add("flutter");
list2.add("kotlin");
list2.removeAt(0);
list2.forEach((item) {
print(item);
});
}
List getList() {
return new List();
}
输入如下:
flutter
kotlin
---------------------------
flutter
kotlin
如有错误,还请指出,谢谢!