显式动画——Transition系列(配合AnimationController)
2.1 循环旋转——RotationTransition
我们需要使用RotationTransition
配合AnimationController
实现动画的手动控制,以实现隐式动画(Animated系列)无法完成的功能:循环重播、随时中断、多方协调等。
另外,要使用AnimationController,我们必须使用StatefulWidget,以使用生命周期函数initState()
和dispose()
。
我们先简单地看一个实例,在后文中,我们将详细解释其中的内容。
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
MyApp({Key? key}) : super(key: key);
@override
_MyAppState createState() => _MyAppState();
}
//SingleTickerProviderStateMixin用于垂直同步
class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
late AnimationController _controller;
bool _loading = false;
@override
void initState() {
_controller = AnimationController(
duration: const Duration(seconds: 1),
vsync: this,
);
super.initState();
}
@override
void dispose() {
_controller.dispose(); //用完删除,否则内存泄漏
super.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Container(
width: 300,
height: 120,
color: Colors.blue,
child: RotationTransition(
turns: _controller, //这里直接使用controller即可,后文来解释
//这里的动画还没有开始,调用_controller.forward()开始
child: const Icon(Icons.repeat,size: 100),
),
)),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () { //按一下按钮开始旋转,再按一下停止。
_loading ? _controller.stop() : _controller.repeat();
_loading = !_loading;
},
),
));
}
}
完成效果如下:
2.2 AnimationController是什么
class AnimationController extends Animation<double>
从本质上来说,AnimationController
是一个Animation<double>
,正如上面看到的那样,RotationTransition
的turns属性本质上要求一个Animation。
AnimationController会在Duration时间内,生成0~1的double,如果要更改区间,使用lowerBound和upperBound属性。
示例代码:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
MyApp({Key? key}) : super(key: key);
@override
_MyAppState createState() => _MyAppState();
}
//SingleTickerProviderStateMixin用于垂直同步,屏幕每次刷新,产生一个tick
class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
/* class AnimationController extends Animation<double> */
late AnimationController _controller;
bool _loading = false;
@override
void initState() {
_controller = AnimationController(
duration: const Duration(seconds: 1),
lowerBound: 0.0, //与默认一致,生成0-1
upperBound: 1.0,
vsync: this,
)..addListener(() { //dart语法:“..”表示返回值还是前面的表达式
// _controller.value 在1s内自动生成0~1之间的数字(1秒60帧)
print("${_controller.value}");
});
super.initState();
}
@override
void dispose() {
_controller.dispose(); //用完删除,否则内存泄漏
super.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Container(
width: 300,
height: 120,
color: Colors.blue,
//例如:RotateTransition、FadeTransition、ScaleTransition
child: FadeTransition(
opacity: _controller, //这里本质上要求一个Animation<double>
child: const Icon(Icons.repeat, size: 100),
),
)),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
_loading ? _controller.stop() : _controller.repeat(reverse: true);
_loading = !_loading;
},
),
));
}
}
2.3 控制器补间(Tween)和曲线
我们可以使用drive函数将Animation的值映射到Tween上,也就是说,最终生成的值是在0.5到1.0之间,而不是原来的0.0~1.0了。
ScaleTransition(
scale: _controller.drive(Tween(begin: 0.5, end: 1.0)),
...
),
又比如说,
SlideTransition(
position: _controller.drive(Tween(begin: Offset.zero, end: const Offset(0.1,0))),
...
),
//等价于
SlideTransition(
position: Tween(begin: Offset.zero, end: const Offset(0.1,0)).animate(_controller),
...
),
通过使用chain函数,Tween之间可以叠加,比如,
SlideTransition(
position: Tween(begin: Offset.zero, end: const Offset(0,1.0))
.chain(CurveTween(curve: Curves.elasticOut)) //叠加曲线
.chain(CurveTween(curve: const Interval(0.5, 1.0)))
//使用Interval,限制动画播放时间,从 50% 时间开始到 100% ,其他时间不做任何事
.animate(_controller),
...
),
整体代码如下:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
MyApp({Key? key}) : super(key: key);
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late bool _stop = false;
@override
void initState() {
_controller = AnimationController(
duration: const Duration(seconds: 1),
vsync: this,
)..repeat();
super.initState();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: SlideTransition(
position: Tween(begin: Offset.zero, end: const Offset(0,1.0))
.chain(CurveTween(curve: Curves.elasticOut)) //叠加曲线
.chain(CurveTween(curve: const Interval(0.5, 1.0)))
//使用Interval,限制动画播放时间,从 50% 时间开始到 100% ,其他时间不做任何事
.animate(_controller),
child: Container(
width: 300,
height: 120,
color: Colors.blue,
))),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
_stop ? _controller.repeat() : _controller.reset();
_stop = !_stop;
},
),
));
}
}
2.4 交错动画——Interval
如上文,使用Interval可以限制播放时间,如此一来,作如下安排,达到五个组件间到交错动画:
-
时间0.0~0.2运行第一个动画
-
时间0.2~0.4运行第二个动画
-
时间0.4~0.6运行第三个动画
-
时间0.6~0.8运行第四个动画
-
时间0.8~1.0运行第五个动画
使用
Column
控件将以上五个SlideTransition
组织起来,并将它们绑定到同一个_controller即可(SlideTransition本身也是Widget):class SlideTransition extends AnimatedWidget { ... }
2.5 自定义动画——AnimatedBuilder
通过自己动手制作FadeTransition,我们可以看出Transition系列控件的内部原理。同时也可以看到,就像TweenAnimationBuilder那样,使用AnimatedBuilder时我们也可以对动画进行优化,避免反复渲染那些与动画无关的Widget:
//如何使用AnimatedBuilder制作出一个FadeTransition?
child: AnimatedBuilder(
//每当animtion(_controller)的值变化,flutter就会重新调用builder函数
animation: _controller,
//还记得builder就是复杂版的child吗?
builder: (context, child) {
return Opacity(
opacity: _controller.value,//这里可以用Tween制作补间动画
child: child
);
},
//下面的child是用于优化
child: const Icon(Icons.repeat, size: 100)
),
//这是内置的FadeTransition,使用起来更加简单,但是没有办法进行细致的绘制优化
child: FadeTransition(
opacity: _controller, //这里本质上要求一个Animation<double>
child: const Icon(Icons.repeat, size: 100),
),
整体代码如下:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
)..repeat();
super.initState();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final Animation opacityAnimation = Tween(begin: 0.5, end: 0.8).animate(_controller);
return MaterialApp(
home: Scaffold(
body: Center(
child: Container(
width: 300,
height: 300,
color: Colors.red,
child: AnimatedBuilder(
animation: _controller, //每当animtion(_controller)的值变化,flutter就会重新调用builder函数
builder: (context, child) { //还记得builder就是复杂版的child吗?
return Opacity(
opacity: opacityAnimation.value,
//使用Tween制作补间
//需要注意的是这里的opacity是普通变量,所以需要使用Animation.value
child: child
);
},
//下面的child是用于优化
child: const Icon(Icons.repeat, size: 100)
))),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {},
),
));
}
}
2.6 使用多个Controller的例子——TickerProviderStateMixin
有时我们会希望使用多个Controller(绝大多数情况下一个足矣),此时我们需要使用TickerProviderStateMixin
。
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with TickerProviderStateMixin {
late AnimationController _expansionController;
late AnimationController _opacityController;
@override
void initState() {
_expansionController = AnimationController(vsync: this);
_opacityController = AnimationController(vsync: this);
super.initState();
}
@override
void dispose() {
_expansionController.dispose();
_opacityController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final Animation gradientAnimation =
Tween(begin: 0.3, end: 1.0).animate(_expansionController);
return MaterialApp(
home: Scaffold(
body: Center(
child: FadeTransition(
opacity: _opacityController.drive(Tween(begin: 1.0, end: 0.5)),
child: AnimatedBuilder(
animation: _expansionController,
builder: (context, child) => Container(
width: 200,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
gradient: RadialGradient(colors: [
Colors.blue.shade500,
Colors.grey.shade50
], stops: [
gradientAnimation.value,
gradientAnimation.value + 0.2
]),
),
),
))),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.badge),
onPressed: () async {
_expansionController.duration = const Duration(seconds: 3);
_expansionController.forward();
await Future.delayed(const Duration(seconds: 3));
_opacityController.duration = const Duration(seconds: 4);
_opacityController.forward();
await Future.delayed(const Duration(seconds: 4));
_expansionController.duration = const Duration(seconds: 1);
_expansionController.reverse();
},
),
));
}
}