大家好,我是 17。
flutter 动画相关的有很多类。 一开始的时候不知道怎么用。本文通过示例,逐步展示相关的类的用法。
为了展示动画原理,我们从 AnimationController 类开始。
最简单的动画
import 'package:flutter/material.dart';
void main() {
runApp(const App());
}
class App extends StatefulWidget {
const App({Key? key}) : super(key: key);
State<App> createState() => _AppState();
}
class _AppState extends State<App> with TickerProviderStateMixin {
late AnimationController _controller;
void initState() {
_controller =
AnimationController(duration: const Duration(seconds: 1), vsync: this)
..repeat()
..addListener(() {
setState(() {});
});
super.initState();
}
Widget build(BuildContext context) {
return Center(child: Container(
color: Colors.green,
width: 100 * _controller.value,
height: 50 * _controller.value,
));
}
}
copy 上面的代码放到 main.dart里 执行,会看到一个不断放大的矩形。
_controller.value
的变化区间是 从 0 到 1。 如果想从 100,200变化呢?加一个 tween
class _AppState extends State<App> with TickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
void initState() {
_controller =
AnimationController(duration: const Duration(seconds: 1), vsync: this)
..repeat()
..addListener(() {
setState(() {});
});
_animation = Tween<double>(begin: 1, end: 2).animate(_controller);
//或这样写
// _animation =_controller.drive(Tween<double>(begin: 1, end: 2));
super.initState();
}
Widget build(BuildContext context) {
return Center(
child: Container(
color: Colors.green,
width: 100 * _animation.value,
height: 50 * _animation.value,
));
}
}
也许你会问,为什么加了一个_animation。 因为 tween 的 animate 方法会生成一个 新的 Animation对象。这样新的对象把原来的 [0,1] 区间映射成 tweeen 规定的区间 做数据输出。 新对象的职责是提供数据,原来的 _controller
对动画进行控制。
目前的数据的变化是线性的,想变成 ease 呢?加一个 curve即可
_animation = Tween<double>(begin: 1, end: 2)
.chain(CurveTween(curve: Curves.ease))
.animate(_controller);
这样写也是可以的
_animation = _controller
.drive(Tween<double>(begin: 1, end: 2))
.drive(CurveTween(curve: Curves.ease));
至此,动画的精华就介绍完了。
动画性能
上面的示例中用的是整页刷新,为了能局部刷新 flutter 提供了两个组件
AnimatedWidget
它的作用就是把 setState 方法封装了起来,我们不用手动调用,而且把刷新限制在了 AnimatedWidget之内。下面用 AnimatedWidget 把开始的示例改造一下。
import 'package:flutter/material.dart';
void main() {
runApp(const App());
}
class App extends StatefulWidget {
const App({Key? key}) : super(key: key);
State<App> createState() => _AppState();
}
class _AppState extends State<App> with TickerProviderStateMixin {
late AnimationController _controller;
void initState() {
_controller =
AnimationController(duration: const Duration(seconds: 1), vsync: this)
..repeat();
super.initState();
}
Widget build(BuildContext context) {
return Center(
child: MyWidget(
animation: _controller,
));
}
}
class MyWidget extends AnimatedWidget {
const MyWidget({Key? key, required this.animation})
: super(key: key, listenable: animation);
final Animation<double> animation;
Widget build(BuildContext context) {
return Container(
color: Colors.green,
width: 100 * animation.value,
height: 50 * animation.value,
);
}
}
用 AnimatedWidget 就不需要再手动 setState了。但每次都需要把动画 包装成一个widget。
AnimatedBuilder
不需要把动画部分包装成 Widget。只把 build函数修改下即可。
Widget build(BuildContext context) {
return Center(
child: AnimatedBuilder(
animation: _controller,
builder: (context, _) {
return Container(
color: Colors.green,
width: 100 * _controller.value,
height: 50 * _controller.value,
);
},
));
}
本例中的 container没有child ,所以不会有性能问题,但是如果 container有一个复杂的child,每次都重建,这个成本还是很高的。需要优化一下
Widget build(BuildContext context) {
var child = Container(
color: Colors.lightBlue,
);
return Center(
child: AnimatedBuilder(
animation: _controller,
child: child,
builder: (context, child) {
return Container(
color: Colors.green,
width: 100 * _controller.value,
height: 50 * _controller.value,
child: child,
);
},
));
}
}
这里简单的用一个蓝色container做为 child,实际中可能 是一个 widget。用child参数的好处是提高性能,child 只 build 一次。
CustomPainter
对于性能要求较高的场合,可以用 CustomPainter 提高性能。
import 'package:flutter/material.dart';
void main() {
runApp(const App());
}
class App extends StatefulWidget {
const App({Key? key}) : super(key: key);
State<App> createState() => _AppState();
}
class _AppState extends State<App> with TickerProviderStateMixin {
late AnimationController _controller;
void initState() {
_controller =
AnimationController(duration: const Duration(seconds: 1), vsync: this)
..repeat();
super.initState();
}
Widget build(BuildContext context) {
return Center(
child: CustomPaint(
painter: MyPainter(animation: _controller),
size: const Size(100, 50),
));
}
}
class MyPainter extends CustomPainter {
final Animation<double> animation;
MyPainter({required this.animation}) : super(repaint: animation);
void paint(Canvas canvas, Size size) {
var rect =
Rect.fromLTWH(0, 0, 100 * animation.value, 100 * animation.value);
canvas.drawRect(
rect,
Paint()
..style = PaintingStyle.fill
..color = Colors.green);
}
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false;
}
}
关键代码就一句 super(repaint: animation);
repaint 是一个 Listenable 对象。只要有变化,就会执行重绘,与 shouldRepaint 返回值无关。
从绘制上下手,无需再 build 任何 widget,坏处是需要自己调用 canvas 绘制,成本较高。
至此,动画的核心知识就讲完了。剩下的都是锦上添花的东西了。剩下的都不知道也没有关系,知道了会方便与别人沟通。
显式动画
显式动画是指需要提供动画控制器的类
AnimatedWidget 扩展类
- ScaleTransition
- RotationTransition
- SizeTransition
- DefaultTextStyleTransition AnimatedModalBarrier
- RelativePositionedTransition
- SlideTransition
- AlignTransition
- DecoratedboxTransition
- PositionedTransition
这些类知道名字就好,对于不好理解的我都加了链接,可以看文档,上面有动画效果或说明。
AnimatedBuilder 也是继承自 AnimatedWidget。
其它类
FadeTransition 它不是继承自 AnimatedWidget ,通过动画控制器来控制透明度。
final Animation<double> opacity;
与之相关的是 AnimatedOpacity,和 FadeTransition 功能一样,只是不需要动画控制器。用 double数值控制透明度
final double opacity;
隐式动画
隐式动画是指不需要提供动画控制器的类,改变属性就可以触发动画。 包括
- AnimatedPadding
- AnimatedAlign
- AnimatedTheme
- TweenAnimationBuilder
- AnimatedPositionedDirectional
- AnimatedPositioned
- SliverAnimatedOpacity
- AnimatedContainer
- AnimatedPhysicalModel
- AnimatedDefaultTextStyle
- AnimatedOpacity
。一般是以 Animated 开头。
到这就差不多了,还有一些动画组件,也可以归为显示动画或隐式动画之列。
本文的目的,是为了让你对动画有一个轮廓级别的认识。知道类的作用,怎么把它们关联起来, 细节可以去查文档或看源码。