flutter 动画相关的有很多类。 一开始的时候不知道怎么用。本文通过示例,逐步展示相关的类的用法。
为了展示动画原理,我们从 AnimationController 类开始。
最简单的动画
import 'package:flutter/material.dart'; void main() { runApp(const App()); } class App extends StatefulWidget { const App({Key? key}) : super(key: key); @override State<App> createState() => _AppState(); } class _AppState extends State<App> with TickerProviderStateMixin { late AnimationController _controller; @override void initState() { _controller = AnimationController(duration: const Duration(seconds: 1), vsync: this) ..repeat() ..addListener(() { setState(() {}); }); super.initState(); } @override 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; @override 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(); } @override 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); @override State<App> createState() => _AppState(); } class _AppState extends State<App> with TickerProviderStateMixin { late AnimationController _controller; @override void initState() { _controller = AnimationController(duration: const Duration(seconds: 1), vsync: this) ..repeat(); super.initState(); } @override 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; @override 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); @override State<App> createState() => _AppState(); } class _AppState extends State<App> with TickerProviderStateMixin { late AnimationController _controller; @override void initState() { _controller = AnimationController(duration: const Duration(seconds: 1), vsync: this) ..repeat(); super.initState(); } @override 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); @override 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); } @override 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 开头。
到这就差不多了,还有一些动画组件,也可以归为显示动画或隐式动画之列。
本文的目的,是为了让你对动画有一个轮廓级别的认识。知道类的作用,怎么把它们关联起来, 细节可以去查文档或看源码。