动画
Flutter中的动画系统基于`Animation`对象的,和之前的手势不同,它不是一个Widget,这是因为`Animation`对象本身和UI渲染没有任何关系。Animation是一个抽象类,就相当于一个定时器,它用于保存动画的插值和状态,并执行数值的变化。widget可以在`build`函数中读取`Animation`对象的当前值, 并且可以监听动画的状态改变。
AnimationController
AnimationController用于控制动画,它包含动画的启动`forward()`、停止`stop()` 、反向播放 `reverse()`等方法。AnimationController会在动画的每一帧,就会生成一个新的值。默认情况下,AnimationController在给定的时间段内线性的生成从0.0到1.0(默认区间)的数字。
1 2 3 4 5 6
| AnimationController controller = AnimationController( duration: const Duration(milliseconds: 2000), lowerBound: 10.0, upperBound: 20.0, vsync: this );
|
Ticker
Ticker的作用是添加屏幕刷新回调,每次屏幕刷新都会调用`TickerCallback`。使用Ticker来驱动动画会防止屏幕外动画(动画的UI不在当前屏幕时,如锁屏时)消耗不必要的资源。因为Flutter中屏幕刷新时会通知Ticker,锁屏后屏幕会停止刷新,所以Ticker就不会再触发。最简单的做法为将`SingleTickerProviderStateMixin`添加到State的定义中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
| import 'package:flutter/material.dart';
void main() => runApp(AnimationApp());
class AnimationApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: "animation", home: Scaffold( appBar: AppBar( title: Text('animation'), ), body: AnimWidget(), ), ); } }
class AnimWidget extends StatefulWidget { @override State<StatefulWidget> createState() { return _AnimWidgetState(); } }
class _AnimWidgetState extends State<AnimWidget> with SingleTickerProviderStateMixin { AnimationController controller; bool forward = true;
@override void initState() { super.initState(); controller = AnimationController( duration: Duration(milliseconds: 2000), lowerBound: 10.0, upperBound: 100.0, vsync: this, ); controller ..addStatusListener((AnimationStatus status) { debugPrint("状态:$status"); }) ..addListener(() { setState(() => {}); });
debugPrint("controller.value:${controller.value}"); }
@override Widget build(BuildContext context) { return Column( children: <Widget>[ Container( width: controller.value, height: controller.value, color: Colors.blue, ), RaisedButton( child: Text("播放"), onPressed: () { if (forward) { controller.forward(); } else { controller.reverse(); } forward = !forward; }, ), RaisedButton( child: Text("停止"), onPressed: () { controller.stop(); }, ) ], ); } }
|
动画状态监听:在forword结束之后状态为completed。在reverse结束之后状态为dismissed
Tween
默认情况下,`AnimationController`对象值为:double类型,范围是0.0到1.0 。如果我们需要不同的范围或不同的数据类型,则可以使用Tween来配置动画以生成不同的范围或数据类型的值。要使用Tween对象,需要调用其`animate()`方法,然后传入一个控制器对象,同时动画过程中产生的数值由`Tween`的`lerp`方法决定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
| import 'package:flutter/material.dart';
void main() => runApp(AnimationApp());
class AnimationApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: "animation", home: Scaffold( appBar: AppBar( title: Text('animation'), ), body: AnimWidget(), ), ); } }
class AnimWidget extends StatefulWidget { @override State<StatefulWidget> createState() { return _AnimWidgetState(); } }
class _AnimWidgetState extends State<AnimWidget> with SingleTickerProviderStateMixin { AnimationController controller;
bool forward = true;
Tween<Color> tween;
@override void initState() { super.initState();
controller = AnimationController( duration: Duration(milliseconds: 2000), vsync: this, ); tween = ColorTween(begin: Colors.blue, end: Colors.yellow); tween.animate(controller)..addListener(() => setState(() {})); }
@override Widget build(BuildContext context) { return Column( children: <Widget>[ Container( width: 100, height: 100, color: tween.evaluate(controller), ), RaisedButton( child: Text("播放"), onPressed: () { if (forward) { controller.forward(); } else { controller.reverse(); } forward = !forward; }, ), RaisedButton( child: Text("停止"), onPressed: () { controller.stop(); }, ) ], ); } }
|
Curve
动画过程默认是线性的(匀速),如果需要非线形的,比如:加速的或者先加速后减速等。Flutter中可以通过Curve(曲线)来描述动画过程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| import 'package:flutter/material.dart';
void main() => runApp(AnimationApp());
class AnimationApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: "animation", home: Scaffold( appBar: AppBar( title: Text('animation'), ), body: AnimWidget(), ), ); } }
class AnimWidget extends StatefulWidget { @override State<StatefulWidget> createState() { return _AnimWidgetState(); } }
class _AnimWidgetState extends State<AnimWidget> with SingleTickerProviderStateMixin { AnimationController controller; Animation<double> animation; bool forward = true;
@override void initState() { super.initState();
controller = AnimationController( duration: Duration(milliseconds: 2000), vsync: this, );
animation = CurvedAnimation(parent: controller, curve: Curves.bounceIn); animation = Tween(begin: 10.0, end: 100.0).animate(animation) ..addListener(() { setState(() => {}); }); }
@override Widget build(BuildContext context) { return Column( children: <Widget>[ Container( width: animation.value, height: animation.value, color: Colors.blue, ), RaisedButton( child: Text("播放"), onPressed: () { if (forward) { controller.forward(); } else { controller.reverse(); } forward = !forward; }, ), RaisedButton( child: Text("停止"), onPressed: () { controller.stop(); }, ) ], ); } }
|
通过上面的学习我们能够感受到`Animation`对象本身和UI渲染没有任何关系。而通过`addListener()`和`setState()` 来更新UI这一步其实是通用的,如果每个动画中都加这么一句是比较繁琐的。AnimatedWidget类封装了调用`setState()`的细节,简单来说就是自动调用`setState()`。
Flutter中已经封装了很多动画,比如对widget进行缩放,可以直接使用`ScaleTransition`
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| import 'package:flutter/material.dart';
void main() => runApp(AnimationApp());
class AnimationApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: "animation", home: Scaffold( appBar: AppBar( title: Text('animation'), ), body: AnimWidget(), ), ); } }
class AnimWidget extends StatefulWidget { @override State<StatefulWidget> createState() { return _AnimWidgetState(); } }
class _AnimWidgetState extends State<AnimWidget> with SingleTickerProviderStateMixin { AnimationController controller; Animation<double> animation; bool forward = true;
@override void initState() { super.initState();
controller = AnimationController( duration: Duration(milliseconds: 2000), vsync: this, );
animation = CurvedAnimation(parent: controller, curve: Curves.bounceIn); animation = Tween(begin: 10.0, end: 100.0).animate(animation); }
@override Widget build(BuildContext context) { return Column( children: <Widget>[ ScaleTransition( child: Container( width: 100, height: 100, color: Colors.blue, ), scale: controller, ), RaisedButton( child: Text("播放"), onPressed: () { if (forward) { controller.forward(); } else { controller.reverse(); } forward = !forward; }, ), RaisedButton( child: Text("停止"), onPressed: () { controller.stop(); }, ) ], ); } }
|
Hero动画
Hero动画就是在路由切换时,有一个共享的Widget可以在新旧路由间切换,由于共享的Widget在新旧路由页面上的位置、外观可能有所差异,所以在路由切换时会逐渐过渡,这样就会产生一个Hero动画。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter Demo', home: Scaffold( appBar: AppBar( title: Text("主页"), ), body: Route1()), ); } }
class Route1 extends StatelessWidget { @override Widget build(BuildContext context) { return Container( alignment: Alignment.topCenter, child: InkWell( child: Hero( tag: "avatar", child: CircleAvatar( backgroundImage: AssetImage( "assets/banner.jpeg", ), ), ), onTap: () { Navigator.push(context, MaterialPageRoute(builder: (_) { return Route2(); })); }, ), ); } }
class Route2 extends StatelessWidget { @override Widget build(BuildContext context) { return Center( child: Hero( tag: "avatar", child: Image.asset("assets/banner.jpeg")), ); } }
|
组合动画
有些时候我们可能会需要执行一个动画序列执行一些复杂的动画。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
| import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter Demo', home: Route(), ); } }
class Route extends StatefulWidget { @override State<StatefulWidget> createState() { return RouteState(); } }
class RouteState extends State<Route> with SingleTickerProviderStateMixin { Animation<Color> color; Animation<double> width; AnimationController controller;
@override void initState() { super.initState(); controller = AnimationController( duration: Duration(milliseconds: 2000), vsync: this, );
width = Tween<double>( begin: 100.0, end: 300.0, ).animate( CurvedAnimation( parent: controller, curve: Interval( 0.0, 0.6, ), ), );
color = ColorTween( begin: Colors.green, end: Colors.red, ).animate( CurvedAnimation( parent: controller, curve: Interval( 0.6, 1.0, ), ), ); }
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("主页"), ), body: InkWell( child: AnimatedBuilder( animation: controller, builder: (context, child) { return Container( color: color.value, width: width.value, height: 100.0, ); }), onTap: () { controller.forward().whenCompleteOrCancel(() => controller.reverse()); }, ), ); } }
|