flutter动画
这个转自我自己的有道云 想看图片去那里
文档:Day 3_27 动画和主题.md
链接:http://note.youdao.com/noteshare?id=25480e40efb32250f985ddfa5a9e5fcc&sub=9A5D6A118E9F43AC86D1F69822336C70
动画
动画
不论是web还是前端我们都有较大的作用 所以 我们一般会有专门的Api来实现这个动画
flutter它是有自己的渲染闭环的 所以我们可以给他提供数据然后让他来渲染
我们现在如果想要在手机端使用这个动画 有一些类可以帮我们是实现这个动画
这个动画其实就是设置一个值然后然后让他不断变化
最主要是四个类
1. Animation 这个是一个抽象类
我们可以使用 addListenter来见提供它 动画的过程中发生的改变
removeListener移除监听器
addStatusListener 可以监听 状态
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-edrr1DUH-1588228670302)(2B4D183A398643C8B0EA8C82BC51CFF8)]
Animation: 抽象类
- 监听动画值的改变
- 监听动画状态的改变
- value 获得动画的值
- status 获得动画的状态
但是这个Animation是不能直接使用的
如果我们想使用的话 我们就使用AnimationController
它继承至我们的Animation 而且这个东西还有其他的东西
我们的动画就是不断改变某一个值的过程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZvnN9Hp5-1588228670307)(06BF9C71E9D44E26900DBA2124B1AD4E)]
还有lowerBound upperBound 最小值 最大值
vsync 这个东西是一个同步信号 这个东西是渲染的时候使用的 它自己有一个渲染的闭环 所以这个东西就是一个屏幕的刷新率
Animation: 抽象类
- 监听动画值的改变
- 监听动画状态的改变
- value 获得动画的值
- status 获得动画的状态
AnimationController 继承至Animation - vsync 这个东西 它这个东西需要将this传进去的 但是很显然这个this不是它的类型的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TpUUGqvi-1588228670309)(6F6A60EA7BFB4DD180CE814F273891FC)]
因为我们flutter是单继承的结构 所以很明显 肯定已经不能使用继承的方法来使这个this变成对应方法
我们这里使用混入 这个混入我们可以使被混入的类具有对应的类的所有的相关的属性和方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sGpMlQO7-1588228670312)(B74993E7E2DF44848C570162FA52B375)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6eDAs5De-1588228670314)(EFFEDAE0FEC046E6BE6AABEA36841A33)]
很明显这个是继承这这个TickerProvider 这个类的
但是这里报错了 因为它这里是有一个条件的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1Pp7WPMe-1588228670316)(81C258B944CD4AA98F28F023F2FF7285)]
它这里要求混入的类必须是继承至StatefulWidget的 而且有一个对应的State 我们需要把这个东西 放在State里面
我们知道我们接受到同步信号是为了进行下一帧的绘制的 所以如果不接受到这个信号它就是不会进行绘制的
这个东西主要是出于性能的考虑 因为如果屏幕不变的话我们使用 就可以让他不进行屏幕的刷新
还有就是我们如果做要给不断变大的画面的话
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-05vzvCrc-1588228670318)(168A182A51E140E9BC8A629F3E691F58)]
它就会不断的进行绘制 这样它的效率就会比较低 但是如果我们当前把这个东西退到后台了
我们就不需要它在进行绘制了 这个时候我们就没有收到这个同步信号 它就不能执行这个东西
虽然ios是对这个推到后台的进行有做控制的 但是android 是没有做控制的
所以做了这个东西它就不会在后台频繁的刷新页面了
- Animation: 抽象类
- 监听动画值的改变
- 监听动画状态的改变
- value 获得动画的值
- status 获得动画的状态
- AnimationController 继承至Animation
- vsync 这个东西 它这个东西需要将this传进去的 但是很显然这个this不是它的类型的
- vsync: 同步信号(this -> with SingleTickerProviderStateMixin)所以为了我们能将这个参数传进去我们需要传这个参数
- forward(): 向前执行动画
- reverse(): 反转执行动画
- CurvedAnimation:
- 作用: 设置动画执行的速率(速度曲线)
- 这个速度曲线就和js里面的那个变化是一样的 但是它有很多的类型
CurvedAnimation
class HYHomeScreen extends StatefulWidget {
@override
_HYHomeScreenState createState() => _HYHomeScreenState();
}
class _HYHomeScreenState extends State<HYHomeScreen> with SingleTickerProviderStateMixin {
@override
Widget build(BuildContext context) {
final controller = AnimationController(vsync: this);
CurvedAnimation(parent: controller,curve: Curves.ease );
return Scaffold(
appBar: AppBar(
title: Text("title")
),
body: Text("hello"),
);
}
}
这个Curves和Colors一样的
这个的效果有很多
我们可以去官网上找这个效果的类型
https://api.flutter.dev/flutter/animation/Curves-class.html
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3L7oGLlI-1588228670319)(75EDB21E66EB405F8D1E01F3BF0BC143)]
这个CurvedAnimation就是设置这个东西
还有一个类就是Tween
- Animation: 抽象类
- 监听动画值的改变
- 监听动画状态的改变
- value 获得动画的值
- status 获得动画的状态
- AnimationController 继承至Animation
- vsync 这个东西 它这个东西需要将this传进去的 但是很显然这个this不是它的类型的
- vsync: 同步信号(this -> with SingleTickerProviderStateMixin)所以为了我们能将这个参数传进去我们需要传这个参数
- forward(): 向前执行动画
- reverse(): 反转执行动画
- CurvedAnimation:
- 作用: 设置动画执行的速率(速度曲线)
- 这个速度曲线就和js里面的那个变化是一样的 但是它有很多的类型
- Tween: 设置动画执行value的范围
- begin: 开始的值
- end: 结束的值
我们之前看到Controller是可以设置这个东西范围的
它有两个参数的
lowerBound upperBound 这个东西可以设置它范围
但是这里个东西是有一个限制的
就是Controller如果要传给这个CurvedAnimation upperBound lowerBound就必须是 0 - 1的范围的
如果我们这样设置它就会报错
所以如果你想要给他设置然后它又不是0 - 1那我们就不会这样设置它
final controller = AnimationController(vsync: this, upperBound: 200, lowerBound: 100);
CurvedAnimation(parent: controller,curve: Curves.ease );
这个时候我们就可以使用Tween
我们可以调用它的一个方法.animate
class HYHomeScreen extends StatefulWidget {
@override
_HYHomeScreenState createState() => _HYHomeScreenState();
}
class _HYHomeScreenState extends State<HYHomeScreen> with SingleTickerProviderStateMixin {
@override
Widget build(BuildContext context) {
final controller = AnimationController(vsync: this, upperBound: 200, lowerBound: 100);
final animation = CurvedAnimation(parent: controller,curve: Curves.ease );
Tween(begin: 100, end: 200).animate(animation);
return Scaffold(
appBar: AppBar(
title: Text("title")
),
body: Text("hello"),
);
}
}
我们这个animate可以给他传入这个controller animation CurvedAnimation的参数
如果是使用这个CurvedAniamtion 的对象 我们就可以加上这个Curved的变化
这里就会得到一个valueAnimation 的值 这样我们就可以到其他的对方使用这个值 让它动态的变化
- Animation: 抽象类
- 监听动画值的改变
- 监听动画状态的改变
- value 获得动画的值
- status 获得动画的状态
- AnimationController 继承至Animation
- vsync 这个东西 它这个东西需要将this传进去的 但是很显然这个this不是它的类型的
- vsync: 同步信号(this -> with SingleTickerProviderStateMixin)所以为了我们能将这个参数传进去我们需要传这个参数
- forward(): 向前执行动画
- reverse(): 反转执行动画
- CurvedAnimation:
- 作用: 设置动画执行的速率(速度曲线)
- 这个速度曲线就和js里面的那个变化是一样的 但是它有很多的类型
- Tween: 设置动画执行value的范围
- begin: 开始的值
- end: 结束的值
flutter里面如果是一个文档注释 它是一个特有的格式不能随便写
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KG7nYE6L-1588228670321)(73829BDBAB3A4D90833AEA80CF4868D1)]
不按它要求写就会包这种错误
这个时候这种注释我们可以放到其他地方例如里面 或者 是最后
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GDEvBffG-1588228670322)(1964DEE92B73494B96FD5FFB56FE815D)]
我们来做第一个动画
心跳的动画
class HYHomeScreen extends StatefulWidget {
@override
_HYHomeScreenState createState() => _HYHomeScreenState();
}
class _HYHomeScreenState extends State<HYHomeScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("title")
),
body: Center(
child: Icon(Icons.favorite, color: Colors.red, size: 50)
),
);
}
}
如果我们想做一个动画的化我们都要先创建一个AnimationController 这个东西是做动画的基石
同时注意我们这里不能直接在对应的声明变量的位置给变量赋值
class _HYHomeScreenState extends State<HYHomeScreen> with SingleTickerProviderStateMixin {
AnimationController _controller = AnimationController(vsync: this);
...
所以我们需要在某一个方法里面写this
class _HYHomeScreenState extends State<HYHomeScreen> with SingleTickerProviderStateMixin {
AnimationController _controller;
@override
void initState() {
// TODO: implement initState
super.initState();
_controller = AnimationController(vsync: this);
}
这样我们就可以使用它了
还有就是需要指定执行时间之类的参数
使用的时候是将这个_controller.value这样的方式使用
class _HYHomeScreenState extends State<HYHomeScreen> with SingleTickerProviderStateMixin {
AnimationController _controller;
@override
void initState() {
// TODO: implement initState
super.initState();
_controller = AnimationController(vsync: this, duration: Duration(seconds: 1), lowerBound: 50, upperBound: 150);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("title")
),
body: Center(
child: Icon(Icons.favorite, color: Colors.red, size: _controller.value)
),
);
}
}
然后来看看呢
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fRmNrDin-1588228670324)(C1B1AB7E2786405AB6169062CE5A96B3)]
但是并没有出现动画
这个是因为我们并没有让它开始 当你点击下面的按钮的时候它就开始执行动画
class _HYHomeScreenState extends State<HYHomeScreen> with SingleTickerProviderStateMixin {
AnimationController _controller;
@override
void initState() {
// TODO: implement initState
super.initState();
_controller = AnimationController(vsync: this, duration: Duration(seconds: 1), lowerBound: 50, upperBound: 150);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("title")
),
body: Center(
child: Icon(Icons.favorite, color: Colors.red, size: _controller.value)
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.play_arrow),
onPressed: () {
_controller.forward();
},
),
);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TSfRTMOV-1588228670325)(C3388D3354D64BE0A727E57D66C08F6E)]
但是它还是没有动画效果
因为我们想要改变这个值使它的界面发生变化的 我们必须让它的界面发生刷新
刷新之后重新构建我们的Icon才能使界面发生一个比较大的图标
那么我们因该怎么办呢
这里我们需要在initState里面再写回调函数
- 最基础的动画效果
class _HYHomeScreenState extends State<HYHomeScreen> with SingleTickerProviderStateMixin {
AnimationController _controller;
@override
void initState() {
// TODO: implement initState
super.initState();
// 1. 创建AnimationController
_controller = AnimationController(vsync: this, duration: Duration(seconds: 2), lowerBound: 50, upperBound: 150);
// 2.
_controller.addListener(() {
// 这里就是每次数据变化的时候我们调用setState让界面发生刷新
setState(() {
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("title")
),
body: Center(
child: Icon(Icons.favorite, color: Colors.red, size: _controller.value)
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.play_arrow),
onPressed: () {
_controller.forward();
},
),
);
}
}
但是这样的可能会出现一个问题就是 我们的新能会比较低 因为我们的其他的东西是不需要发生刷新的
这个后面我们再说
我想设置一个Curve的效果
- 改变动画的变化曲线
我们需要将这个animation设为全局变量(类的变量)让其他的地方可以使用这个变量
class _HYHomeScreenState extends State<HYHomeScreen> with SingleTickerProviderStateMixin {
// 创建AnimationController
AnimationController _controller;
// 我们需要在其他的地方使用 所以我们要让他变成全局变量
Animation _animation;
@override
void initState() {
// TODO: implement initState
super.initState();
// 1. 创建AnimationController
_controller = AnimationController(vsync: this, duration: Duration(seconds: 2), lowerBound: 50, upperBound: 150);
// 2.设置Curve值
_animation = CurvedAnimation(parent: _controller, curve: Curves.elasticIn);
// 监听动画值的改变
_controller.addListener(() {
// 这里就是每次数据变化的时候我们调用setState让界面发生刷新
setState(() {
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("title")
),
body: Center(
child: Icon(Icons.favorite, color: Colors.red, size: _animation.value)
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.play_arrow),
onPressed: () {
_controller.forward();
},
),
);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pH63Hao2-1588228670327)(58F89015370744EE88FC05F1DDEBD8E3)]
但是现在报错了 因为我们之前说过 如果要将这个参数传过来这个值的范围是0 - 1的
_controller = AnimationController(vsync: this, duration: Duration(seconds: 2), lowerBound: 50, upperBound: 150);
其实这里Curve是做了一个断言
所以我们这里lowerBound 和 upperBound 的范围只能在 0.0 - 1.0
如果就是0.0 - 1.0 那你可以不写因为默认就是 0.0 - 1.0
去掉这两个参数就对了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ctIkHH45-1588228670328)(3E70A352B4DD454898BF351A37719F2D)]
class _HYHomeScreenState extends State<HYHomeScreen> with SingleTickerProviderStateMixin {
// 创建AnimationController
AnimationController _controller;
// 我们需要在其他的地方使用 所以我们要让他变成全局变量
Animation _animation;
Animation _sizeAnimation;
@override
void initState() {
// TODO: implement initState
super.initState();
// 1. 创建AnimationController
_controller = AnimationController(vsync: this, duration: Duration(seconds: 2));
// 2.设置Curve值
_animation = CurvedAnimation(parent: _controller, curve: Curves.elasticIn);
// 3. Tween
// 这个东西返回的也是一个Animation 所以我们可以这样来赋值animation = 的方式来做
_sizeAnimation = Tween(begin: 50, end: 150).animate(_animation);
// 监听动画值的改变
_controller.addListener(() {
// 这里就是每次数据变化的时候我们调用setState让界面发生刷新
setState(() {
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("title")
),
body: Center(
child: Icon(Icons.favorite, color: Colors.red, size: _sizeAnimation.value)
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.play_arrow),
onPressed: () {
_controller.forward();
},
),
);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SRpeAPow-1588228670329)(01400AC62BA44105AACCC19CA7A1519A)]
但是这里又报错了 它说我们这里的值是int类型的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ntStj4U6-1588228670331)(3740C44CBF9E4946897554F020240327)]
这里有一个泛型说明我们这里的传入的泛型是int
所以改一下
class _HYHomeScreenState extends State<HYHomeScreen> with SingleTickerProviderStateMixin {
// 创建AnimationController
AnimationController _controller;
// 我们需要在其他的地方使用 所以我们要让他变成全局变量
Animation _animation;
Animation _sizeAnimation;
@override
void initState() {
// TODO: implement initState
super.initState();
// 1. 创建AnimationController
_controller = AnimationController(vsync: this, duration: Duration(seconds: 2));
// 2.设置Curve值
_animation = CurvedAnimation(parent: _controller, curve: Curves.elasticIn);
// 3. Tween
// 这个东西返回的也是一个Animation 所以我们可以这样来赋值animation = 的方式来做
_sizeAnimation = Tween(begin: 50.0, end: 150.0).animate(_animation);
// 监听动画值的改变
_controller.addListener(() {
// 这里就是每次数据变化的时候我们调用setState让界面发生刷新
setState(() {
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("title")
),
body: Center(
child: Icon(Icons.favorite, color: Colors.red, size: _sizeAnimation.value)
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.play_arrow),
onPressed: () {
_controller.forward();
},
),
);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KP9xfZaT-1588228670332)(10A303564C9B478D88CD62BAC557DDB7)]
这里我们把代码重新跑过了
这个会有一个回退的效果其他的效果看这个文档
https://api.flutter.dev/flutter/animation/Curves-class.html
那现在我们不满足这样的一个基本使用
我们现在是由小变大的 我们希望它呢再由大变小 然后反复
这样我们就要监听动画的状态改变
@override
void initState() {
// TODO: implement initState
super.initState();
// 1. 创建AnimationController
_controller = AnimationController(vsync: this, duration: Duration(seconds: 2));
// 2.设置Curve值
_animation = CurvedAnimation(parent: _controller, curve: Curves.elasticIn);
// 3. Tween
// 这个东西返回的也是一个Animation 所以我们可以这样来赋值animation = 的方式来做
_sizeAnimation = Tween(begin: 50.0, end: 150.0).animate(_animation);
// 监听动画值的改变
_controller.addListener(() {
// 这里就是每次数据变化的时候我们调用setState让界面发生刷新
setState(() {
});
});
// 监听动画的状态改变
_controller.addStatusListener((status) {
});
}
但是这里我们不能写的一样 因为它这里是有传过来一个参数的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J7h9hxu9-1588228670334)(A056C928F18246E39A44BAF1985BDB5A)]
我们可以拿到这个status这个东西
// 监听动画的状态改变
_controller.addStatusListener((status) {
if( status == AnimationStatus.completed ) {
}
});
这里我们就可以做判断了 这里的Status是一个枚举类型
我们这里首先要返回 所以监听完成
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rwxX6ryj-1588228670335)(E57B2CBE220747339894D42AFE237544)]
class _HYHomeScreenState extends State<HYHomeScreen> with SingleTickerProviderStateMixin {
// 创建AnimationController
AnimationController _controller;
// 我们需要在其他的地方使用 所以我们要让他变成全局变量
Animation _animation;
Animation _sizeAnimation;
@override
void initState() {
// TODO: implement initState
super.initState();
// 1. 创建AnimationController
_controller = AnimationController(vsync: this, duration: Duration(seconds: 2));
// 2.设置Curve值
_animation = CurvedAnimation(parent: _controller, curve: Curves.elasticIn);
// 3. Tween
// 这个东西返回的也是一个Animation 所以我们可以这样来赋值animation = 的方式来做
_sizeAnimation = Tween(begin: 50.0, end: 150.0).animate(_animation);
// 监听动画值的改变
_controller.addListener(() {
// 这里就是每次数据变化的时候我们调用setState让界面发生刷新
setState(() {
});
});
// 监听动画的状态改变
_controller.addStatusListener((status) {
if( status == AnimationStatus.completed ) {
_controller.reverse();
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("title")
),
body: Center(
child: Icon(Icons.favorite, color: Colors.red, size: _sizeAnimation.value)
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.play_arrow),
onPressed: () {
_controller.forward();
},
),
);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MP6C1qzX-1588228670336)(8EE4DA402DA04B74BB63B83C088E8A52)]
如果想要反复我们就再监听回到dismissed的状态
class _HYHomeScreenState extends State<HYHomeScreen> with SingleTickerProviderStateMixin {
// 创建AnimationController
AnimationController _controller;
// 我们需要在其他的地方使用 所以我们要让他变成全局变量
Animation _animation;
Animation _sizeAnimation;
@override
void initState() {
// TODO: implement initState
super.initState();
// 1. 创建AnimationController
_controller = AnimationController(vsync: this, duration: Duration(seconds: 2));
// 2.设置Curve值
_animation = CurvedAnimation(parent: _controller, curve: Curves.elasticIn);
// 3. Tween
// 这个东西返回的也是一个Animation 所以我们可以这样来赋值animation = 的方式来做
_sizeAnimation = Tween(begin: 50.0, end: 150.0).animate(_animation);
// 监听动画值的改变
_controller.addListener(() {
// 这里就是每次数据变化的时候我们调用setState让界面发生刷新
setState(() {
});
});
// 监听动画的状态改变
_controller.addStatusListener((status) {
if( status == AnimationStatus.completed ) {
_controller.reverse();
} else if(status == AnimationStatus.dismissed) {
_controller.forward();
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("title")
),
body: Center(
child: Icon(Icons.favorite, color: Colors.red, size: _sizeAnimation.value)
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.play_arrow),
onPressed: () {
_controller.forward();
},
),
);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ov8NM8v4-1588228670338)(C2F33768F25E49558991983BC80DA2BC)]
- 暂停效果
这个就要来到我们的点击的地方
通过_controller 的isAnimation来判断动画是否在执行完成的
if( _controller.isAnimating ) {
_controller.stop();
} else {
_controller.forward();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("title")
),
body: Center(
child: Icon(Icons.favorite, color: Colors.red, size: _sizeAnimation.value)
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.play_arrow),
onPressed: () {
if( _controller.isAnimating ) {
_controller.stop();
} else {
_controller.forward();
}
},
),
);
}
为了便于观察 我们把这个变成东西弄成线性的
现在就可以暂停 然后再开始了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y5b1P9nK-1588228670339)(D833B79A27C1410C8CC7C05362980E38)]
但是我们可以看到有一个问题就是我们的这个
如果我们是处于 缩小的状态暂停 然后再进行点击的话 我们反而是会变大的
这个就有点不合适了
我们因该可以在这里再搞一个
状态来看看
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("title")
),
body: Center(
child: Icon(Icons.favorite, color: Colors.red, size: _sizeAnimation.value)
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.play_arrow),
onPressed: () {
if( _controller.isAnimating ) {
_controller.stop();
print(_controller.status);
} else {
_controller.forward();
}
},
),
);
}
当然我们也可以搞一个变量来存储这个状态的值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XfC3DmdU-1588228670341)(286B211ABAD141D385B3D586F072670F)]
- 注意最后要将这个controller 销毁掉
虽然这些引用最后会被销毁掉 但是这个东西最后还有一些东西最后也要自己进行一个销毁 所以我们这样做肯定是要好一点的
import "package:flutter/material.dart";
main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "Flutter Demo",
theme: ThemeData(
primarySwatch: Colors.blue,
splashColor: Colors.transparent
),
home: HYHomeScreen(),
);
}
}
class HYHomeScreen extends StatefulWidget {
@override
_HYHomeScreenState createState() => _HYHomeScreenState();
}
class _HYHomeScreenState extends State<HYHomeScreen> with SingleTickerProviderStateMixin {
// 创建AnimationController
AnimationController _controller;
// 我们需要在其他的地方使用 所以我们要让他变成全局变量
Animation _animation;
Animation _sizeAnimation;
bool _isForward = true;
@override
void initState() {
// TODO: implement initState
super.initState();
// 1. 创建AnimationController
_controller = AnimationController(vsync: this, duration: Duration(seconds: 2));
// 2.设置Curve值
_animation = CurvedAnimation(parent: _controller, curve: Curves.linear);
// 3. Tween
// 这个东西返回的也是一个Animation 所以我们可以这样来赋值animation = 的方式来做
_sizeAnimation = Tween(begin: 50.0, end: 150.0).animate(_animation);
// 监听动画值的改变
_controller.addListener(() {
// 这里就是每次数据变化的时候我们调用setState让界面发生刷新
setState(() {
});
});
// 监听动画的状态改变
_controller.addStatusListener((status) {
if( status == AnimationStatus.completed ) {
_controller.reverse();
_isForward != _isForward;
} else if(status == AnimationStatus.dismissed) {
_controller.forward();
_isForward != _isForward;
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("title")
),
body: Center(
child: Icon(Icons.favorite, color: Colors.red, size: _sizeAnimation.value)
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.play_arrow),
onPressed: () {
if( _controller.isAnimating ) {
_controller.stop();
print(_controller.status);
} else {
// 然后在这里使用这个isForward
if(_controller.status == AnimationStatus.reverse) {
_controller.reverse();
} else if(_controller.status == AnimationStatus.forward) {
_controller.forward();
} else {
// 这里可能会有dismissed的情况 如果有问题我们可以在这里再对情况进行划分
_controller.forward();
}
}
},
),
);
}
@override
void dispose() {
// TODO: implement dispose
_controller.dispose();
super.dispose();
}
}
这个就是一个最基本的动画的使用
当然我们也可以直接使用定时器 + StatefulWidget来完成 动画的效果
Timer.periodic(duration, callback) 这个就是定时器
对前面动画的改进
存在的问题
- 现在我们的动画写的代码效率是比较低的
我们每次都需要写这样一段代码
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZdwOSkiC-1588228670342)(26D025A1D09645D191A0DFA4EC554233)]
肯定是比较麻烦的
- setState 每个地方都要写非常的多余
- 我们需要重建Icon但是不需要重建其他的东西这段代码里面执行了 setState 然后里面执行了build方法 里面的所有东西都重新被构建所以他的效率是比较低的
- 优化方案:
- AnimatedWidget
- 将需要执行的动画的Widget放到一个AnimatedWidget中的build方法进行返回
- AnimatedWidget
这里我们创建一个类 不要继承StatelessWidget或者fulWidget
但是我们知道fulWidget里面用的createState的方法 但是这个AnimatedWidget它是自己带的一个build方法
class HYAnimatedIcon extends AnimatedWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return null;
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kY1gltLs-1588228670344)(251F38E784BB47FA89F8913B94BC7B0D)]
然后我们把对应的要展示的Icon拿过来 然后把原来写Icon的地方用这个创建
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wSSkGKL4-1588228670345)(CBC4A0F066F7412C86A6565BC6BDDCD2)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MkM4Hght-1588228670347)(3CBB2C3C38A340308EABAE23E3D7C9CA)]
但是我们这里要用这个动画的东西
class _HYHomeScreenState extends State<HYHomeScreen> with SingleTickerProviderStateMixin {
// 创建AnimationController
AnimationController _controller;
// 我们需要在其他的地方使用 所以我们要让他变成全局变量
Animation _animation;
Animation _sizeAnimation;
bool _isForward = true;
@override
void initState() {
// TODO: implement initState
super.initState();
// 1. 创建AnimationController
_controller = AnimationController(vsync: this, duration: Duration(seconds: 2));
// 2.设置Curve值
_animation = CurvedAnimation(parent: _controller, curve: Curves.linear);
// 3. Tween
// 这个东西返回的也是一个Animation 所以我们可以这样来赋值animation = 的方式来做
_sizeAnimation = Tween(begin: 50.0, end: 150.0).animate(_animation);
// 监听动画值的改变
// _controller.addListener(() {
//// 这里就是每次数据变化的时候我们调用setState让界面发生刷新
// setState(() {
// });
// });
// 监听动画的状态改变
_controller.addStatusListener((status) {
if( status == AnimationStatus.completed ) {
_controller.reverse();
// _isForward != _isForward;
} else if(status == AnimationStatus.dismissed) {
_controller.forward();
// _isForward != _isForward;
}
});
}
@override
Widget build(BuildContext context) {
print("执行 _HYHomeScreenState的build方法");
return Scaffold(
appBar: AppBar(
title: Text("title")
),
body: Center(
child: HYAnimatedIcon(_sizeAnimation),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.play_arrow),
onPressed: () {
if( _controller.isAnimating ) {
_controller.stop();
print(_controller.status);
} else {
// 然后在这里使用这个isForward
if(_controller.status == AnimationStatus.reverse) {
_controller.reverse();
} else if(_controller.status == AnimationStatus.forward) {
_controller.forward();
} else {
// 这里可能会有dismissed的情况 如果有问题我们可以在这里再对情况进行划分
_controller.forward();
}
}
},
),
);
}
@override
void dispose() {
// TODO: implement dispose
_controller.dispose();
super.dispose();
}
}
class HYAnimatedIcon extends AnimatedWidget {
final Animation _sizeAnim;
HYAnimatedIcon(this._sizeAnim);
@override
Widget build(BuildContext context) {
// TODO: implement build
return Icon(Icons.favorite, color: Colors.red, size: _sizeAnim.value);
}
}
这里我们把那个每次都要写的代码注释掉了
但是这里报错了 因为这个
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SNVlnLEk-1588228670348)(ED84EB08D3F448DBB732548C92B57C65)]
我们的AnimatedWidget 需要给它super过去一个参数 所以这里这样做
所以
class HYAnimatedIcon extends AnimatedWidget {
final Animation _sizeAnim;
HYAnimatedIcon(this._sizeAnim):super(listenable: _sizeAnim);
@override
Widget build(BuildContext context) {
// TODO: implement build
return Icon(Icons.favorite, color: Colors.red, size: _sizeAnim.value);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bz0iUt0a-1588228670349)(F35261BF8F5C4ECE871D8D96F184DE6E)]
但是其实这里我们是可以不用写这个 变量的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1yWzQa6r-1588228670351)(6B0CF39EA36243AC9B1A88658FF11B14)]
那怎么做呢 因为我们是把这个 animation是传给了父类的 所以我们是可以取到这个父类的值的
HYAnimatedIcon(Animation anim):super(listenable: anim);
@override
Widget build(BuildContext context) {
// TODO: implement build
Animation anim = listenable;
return Icon(Icons.favorite, color: Colors.red, size: anim.value);
}
这样我们就只需要执行这个HYAnimatedIcon里面的build方法了
import "package:flutter/material.dart";
main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "Flutter Demo",
theme: ThemeData(
primarySwatch: Colors.blue,
splashColor: Colors.transparent
),
home: HYHomeScreen(),
);
}
}
class HYHomeScreen extends StatefulWidget {
@override
_HYHomeScreenState createState() => _HYHomeScreenState();
}
class _HYHomeScreenState extends State<HYHomeScreen> with SingleTickerProviderStateMixin {
// 创建AnimationController
AnimationController _controller;
// 我们需要在其他的地方使用 所以我们要让他变成全局变量
Animation _animation;
Animation _sizeAnimation;
bool _isForward = true;
@override
void initState() {
// TODO: implement initState
super.initState();
// 1. 创建AnimationController
_controller = AnimationController(vsync: this, duration: Duration(seconds: 2));
// 2.设置Curve值
_animation = CurvedAnimation(parent: _controller, curve: Curves.linear);
// 3. Tween
// 这个东西返回的也是一个Animation 所以我们可以这样来赋值animation = 的方式来做
_sizeAnimation = Tween(begin: 50.0, end: 150.0).animate(_animation);
// 监听动画值的改变
// _controller.addListener(() {
//// 这里就是每次数据变化的时候我们调用setState让界面发生刷新
// setState(() {
// });
// });
// 监听动画的状态改变
_controller.addStatusListener((status) {
if( status == AnimationStatus.completed ) {
_controller.reverse();
// _isForward != _isForward;
} else if(status == AnimationStatus.dismissed) {
_controller.forward();
// _isForward != _isForward;
}
});
}
@override
Widget build(BuildContext context) {
print("执行 _HYHomeScreenState的build方法");
return Scaffold(
appBar: AppBar(
title: Text("title")
),
body: Center(
child: HYAnimatedIcon(_sizeAnimation),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.play_arrow),
onPressed: () {
if( _controller.isAnimating ) {
_controller.stop();
print(_controller.status);
} else {
// 然后在这里使用这个isForward
if(_controller.status == AnimationStatus.reverse) {
_controller.reverse();
} else if(_controller.status == AnimationStatus.forward) {
_controller.forward();
} else {
// 这里可能会有dismissed的情况 如果有问题我们可以在这里再对情况进行划分
_controller.forward();
}
}
},
),
);
}
@override
void dispose() {
// TODO: implement dispose
_controller.dispose();
super.dispose();
}
}
class HYAnimatedIcon extends AnimatedWidget {
HYAnimatedIcon(Animation anim):super(listenable: anim);
@override
Widget build(BuildContext context) {
// TODO: implement build
Animation anim = listenable;
return Icon(Icons.favorite, color: Colors.red, size: anim.value);
}
}
最初的问题
- setState 每个地方都要写非常的多余
- 这段代码里面执行了 setState 然后里面执行了build方法 里面的所有东西都重新被构建所以他的效率是比较低的 我们需要重建Icon但是不需要重建其他的东西
优化方案:
-
AnimatedWidget
- 将需要执行的动画的Widget放到一个AnimatedWidget中的build方法进行返回
- 缺点:
- 1.每次如果都需要创建一个类
- 2.构建的Widget有子类, 那么子类依然会重复的build
-
AnimatedBuilder (最优方案)
-
使用AnimatedBuilder的最优方案
我们发现这个 AnimatedBuilder如果这样写的话 它确实解决了 setState哪都要写 和 所有东西都要build的问题
而且 这个没有创建类了
class _HYHomeScreenState extends State<HYHomeScreen> with SingleTickerProviderStateMixin {
// 创建AnimationController
AnimationController _controller;
// 我们需要在其他的地方使用 所以我们要让他变成全局变量
Animation _animation;
Animation _sizeAnimation;
bool _isForward = true;
@override
void initState() {
// TODO: implement initState
super.initState();
// 1. 创建AnimationController
_controller = AnimationController(vsync: this, duration: Duration(seconds: 2));
// 2.设置Curve值
_animation = CurvedAnimation(parent: _controller, curve: Curves.linear);
// 3. Tween
// 这个东西返回的也是一个Animation 所以我们可以这样来赋值animation = 的方式来做
_sizeAnimation = Tween(begin: 50.0, end: 150.0).animate(_animation);
// 监听动画值的改变
// _controller.addListener(() {
//// 这里就是每次数据变化的时候我们调用setState让界面发生刷新
// setState(() {
// });
// });
// 监听动画的状态改变
_controller.addStatusListener((status) {
if( status == AnimationStatus.completed ) {
_controller.reverse();
// _isForward != _isForward;
} else if(status == AnimationStatus.dismissed) {
_controller.forward();
// _isForward != _isForward;
}
});
}
@override
Widget build(BuildContext context) {
print("执行 _HYHomeScreenState的build方法");
return Scaffold(
appBar: AppBar(
title: Text("title")
),
body: Center(
child: AnimatedBuilder(
animation: _controller,
builder: (ctx, child) {
return Icon(Icons.favorite, color: Colors.red, size: _sizeAnimation.value);
}
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.play_arrow),
onPressed: () {
if( _controller.isAnimating ) {
_controller.stop();
print(_controller.status);
} else {
// 然后在这里使用这个isForward
if(_controller.status == AnimationStatus.reverse) {
_controller.reverse();
} else if(_controller.status == AnimationStatus.forward) {
_controller.forward();
} else {
// 这里可能会有dismissed的情况 如果有问题我们可以在这里再对情况进行划分
_controller.forward();
}
}
},
),
);
}
@override
void dispose() {
// TODO: implement dispose
_controller.dispose();
super.dispose();
}
}
优化方案:
-
AnimatedWidget
- 将需要执行的动画的Widget放到一个AnimatedWidget中的build方法进行返回
- 缺点:
- 1.每次如果都需要创建一个类
- 2.构建的Widget有子类, 那么子类依然会重复的build
-
AnimatedBuilder (最优方案)
-
使用AnimatedBuilder的最优方案
我们发现这个 AnimatedBuilder如果这样写的话 它确实解决了 setState哪都要写 和 所有东西都要build的问题
而且 这个没有创建类了
那还有一个问题就如果创建子类 那我们的子类的东西 是否还需要重新构建
其实是不用的 我们在外面创建然后在里面引用就可以了
class _HYHomeScreenState extends State<HYHomeScreen> with SingleTickerProviderStateMixin {
// 创建AnimationController
AnimationController _controller;
// 我们需要在其他的地方使用 所以我们要让他变成全局变量
Animation _animation;
Animation _sizeAnimation;
bool _isForward = true;
@override
void initState() {
// TODO: implement initState
super.initState();
// 1. 创建AnimationController
_controller = AnimationController(vsync: this, duration: Duration(seconds: 2));
// 2.设置Curve值
_animation = CurvedAnimation(parent: _controller, curve: Curves.linear);
// 3. Tween
// 这个东西返回的也是一个Animation 所以我们可以这样来赋值animation = 的方式来做
_sizeAnimation = Tween(begin: 50.0, end: 150.0).animate(_animation);
// 监听动画值的改变
// _controller.addListener(() {
//// 这里就是每次数据变化的时候我们调用setState让界面发生刷新
// setState(() {
// });
// });
// 监听动画的状态改变
_controller.addStatusListener((status) {
if( status == AnimationStatus.completed ) {
_controller.reverse();
// _isForward != _isForward;
} else if(status == AnimationStatus.dismissed) {
_controller.forward();
// _isForward != _isForward;
}
});
}
@override
Widget build(BuildContext context) {
print("执行 _HYHomeScreenState的build方法");
return Scaffold(
appBar: AppBar(
title: Text("title")
),
body: Center(
child: AnimatedBuilder(
animation: _controller,
builder: (ctx, child) {
return Column(
children: <Widget>[
Icon(Icons.favorite, color: Colors.red, size: _sizeAnimation.value),
child
],
);
},
child: Text("直接引用这个child")
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.play_arrow),
onPressed: () {
if( _controller.isAnimating ) {
_controller.stop();
print(_controller.status);
} else {
// 然后在这里使用这个isForward
if(_controller.status == AnimationStatus.reverse) {
_controller.reverse();
} else if(_controller.status == AnimationStatus.forward) {
_controller.forward();
} else {
// 这里可能会有dismissed的情况 如果有问题我们可以在这里再对情况进行划分
_controller.forward();
}
}
},
),
);
}
@override
void dispose() {
// TODO: implement dispose
_controller.dispose();
super.dispose();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-90Bsj66U-1588228670352)(44039BDF14A74382A32D4FE258243D6D)]
所以这个AniamtedBuilder是去掉了上述的所有缺点的最优解
所以开发里面我们用的最多好还是这个东西‘
交织动画
多个动画放到一起来执行
我们来到https://flutter.dev/docs
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YFIXFTg4-1588228670354)(45663C87597A458890D5C9E8495E3F2A)]
CookBook
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k4jz8j1S-1588228670355)(7D781DABEEAB4E52967FA512E0B78E28)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NVKTmrVo-1588228670357)(B392978684A340B4A3BE9BC1FFB32BCD)]
- 交织动画案例
目标:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QKcAbk4p-1588228670358)(820D3430C2FF4D6B8A0B76B2C97E0D4F)]
我们现在想要这里的Container 集成了很多的动画
我们保留大部分的东西 删掉了原来的AnimatedBuilder
- 我们如果要先做动画的话我们可以先 它最基础的东西做出来 先不要直接想动画怎么做
- 而是先将这个 最基础的Container做出来 然后再考虑动画怎么做
class _HYHomeScreenState extends State<HYHomeScreen> with SingleTickerProviderStateMixin {
// 创建AnimationController
AnimationController _controller;
// 我们需要在其他的地方使用 所以我们要让他变成全局变量
Animation _animation;
Animation _sizeAnimation;
bool _isForward = true;
@override
void initState() {
// TODO: implement initState
super.initState();
// 1. 创建AnimationController
_controller = AnimationController(vsync: this, duration: Duration(seconds: 2));
// 2.设置Curve值
_animation = CurvedAnimation(parent: _controller, curve: Curves.linear);
// 3. Tween
// 这个东西返回的也是一个Animation 所以我们可以这样来赋值animation = 的方式来做
// _sizeAnimation = Tween(begin: 50.0, end: 150.0).animate(_animation);
// 监听动画的状态改变
_controller.addStatusListener((status) {
if( status == AnimationStatus.completed ) {
_controller.reverse();
// _isForward != _isForward;
} else if(status == AnimationStatus.dismissed) {
_controller.forward();
// _isForward != _isForward;
}
});
}
@override
Widget build(BuildContext context) {
print("执行 _HYHomeScreenState的build方法");
return Scaffold(
appBar: AppBar(
title: Text("title")
),
body: Center(
child: Container(
width: 100,
height: 100,
color: Colors.red,
)
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.play_arrow),
onPressed: () {
if( _controller.isAnimating ) {
_controller.stop();
print(_controller.status);
} else {
// 然后在这里使用这个isForward
if(_controller.status == AnimationStatus.reverse) {
_controller.reverse();
} else if(_controller.status == AnimationStatus.forward) {
_controller.forward();
} else {
// 这里可能会有dismissed的情况 如果有问题我们可以在这里再对情况进行划分
_controller.forward();
}
}
},
),
);
}
@override
void dispose() {
// TODO: implement dispose
_controller.dispose();
super.dispose();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-02uJReFA-1588228670360)(FF50E80034984A76909D36E16AB8C451)]
然后就改变这个width的值我们就可以完成这个缩放的动画了
同样我们只要不断改变颜色 同样可以改变它这个颜色
透明度 我们有一个Widget 叫做Opacity这个东西 这个东西可以改变 它的子Widget的透明度
然后这个旋转是在transform里面传递的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZtNYWkF4-1588228670361)(79596161282A4AAB8ECEA3E6930F0A20)]
这个transform: 这个东西要求传一个Matrix4 矩阵的参数
这个东西是用来做倾斜的 其实这里是有很多的东西 它都可以创建 但是直接创建太麻烦 我们可以搞一个 Matrix4.rotationZ来进行旋转
Matrix4.rotationZ(radius) 这个东西就是旋转的意思
但是你会发现这个东西它是绕一个点来进行旋转的 没有办法改变 当然可能是没有找到
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CMERbqt5-1588228670363)(E672F5E67A4748E88830076B78A4BBEC)]
除了我们可以直接设置Container的transform的话 我们还可以给Container添加一个Transform 然后设置它的transform属性
@override
Widget build(BuildContext context) {
print("执行 _HYHomeScreenState的build方法");
/**
* 1. 大小的变化
* 2. 颜色变化
* 3. 透明度
* 4. 旋转动画
*/
return Scaffold(
appBar: AppBar(
title: Text("title")
),
body: Center(
child: Opacity(
opacity: .5,
child: Transform(
transform: Matrix4.rotationZ(pi/4),
child: Container(
width: 200,
height: 200,
color: Colors.red,
),
),
)
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.play_arrow),
onPressed: () {
if( _controller.isAnimating ) {
_controller.stop();
print(_controller.status);
} else {
// 然后在这里使用这个isForward
if(_controller.status == AnimationStatus.reverse) {
_controller.reverse();
} else if(_controller.status == AnimationStatus.forward) {
_controller.forward();
} else {
// 这里可能会有dismissed的情况 如果有问题我们可以在这里再对情况进行划分
_controller.forward();
}
}
},
),
);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KxteetMW-1588228670365)(733BB1442988405B84D8F50C1392AD1C)]
可以看出效果差不多 但是我们如果这样就灵活的多
这里我们可以设置它的alignment属性 设置它的旋转中心
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D2SUIZZx-1588228670366)(26C746E177DC4D74B551187951DEEFF2)]
所以我们可以看出 如果我们要做交织动画 无非就是要不断的改变这个值
@override
Widget build(BuildContext context) {
print("执行 _HYHomeScreenState的build方法");
/**
* 1. 大小的变化
* 2. 颜色变化
* 3. 透明度
* 4. 旋转动画
*/
return Scaffold(
appBar: AppBar(
title: Text("title")
),
body: Center(
child: Opacity(
opacity: .5,
child: Transform(
transform: Matrix4.rotationZ(pi/4),
alignment: Alignment.center,
child: Container(
width: 200,
height: 200,
color: Colors.red,
),
),
)
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.play_arrow),
onPressed: () {
if( _controller.isAnimating ) {
_controller.stop();
print(_controller.status);
} else {
// 然后在这里使用这个isForward
if(_controller.status == AnimationStatus.reverse) {
_controller.reverse();
} else if(_controller.status == AnimationStatus.forward) {
_controller.forward();
} else {
// 这里可能会有dismissed的情况 如果有问题我们可以在这里再对情况进行划分
_controller.forward();
}
}
},
),
);
}
然后让我们把所有的东西放上去
- 注意我们这里使用的是最基础的Animation所以我们
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JPYLsC3O-1588228670368)(036B8296C4E248E4941FD648C0F8617A)]
这样就可以了
但是你会看到我们的 HYHomeScreen正在疯狂执行
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P7kN8RtT-1588228670370)(A8CB029ADF1C4914958D7F89B4A74CFF)]
所以不好
那么 AnimatedBuilder走起
@override
Widget build(BuildContext context) {
print("执行 _HYHomeScreenState的build方法");
/**
* 1. 大小的变化
* 2. 颜色变化
* 3. 透明度
* 4. 旋转动画
*/
return Scaffold(
appBar: AppBar(
title: Text("title")
),
body: Center(
child: AnimatedBuilder(
animation: _controller,
builder: (ctx, child) {
return Opacity(
opacity: _opacityAnima.value,
child: Transform(
transform: Matrix4.rotationZ(_radiusAnima.value),
alignment: Alignment.center,
child: Container(
width: _sizeAnimation.value,
height: _sizeAnimation.value,
color: _colorAnima.value,
),
),
);
},
)
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.play_arrow),
onPressed: () {
if( _controller.isAnimating ) {
_controller.stop();
print(_controller.status);
} else {
// 然后在这里使用这个isForward
if(_controller.status == AnimationStatus.reverse) {
_controller.reverse();
} else if(_controller.status == AnimationStatus.forward) {
_controller.forward();
} else {
// 这里可能会有dismissed的情况 如果有问题我们可以在这里再对情况进行划分
_controller.forward();
}
}
},
),
);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-idnmxGB1-1588228670371)(BF59637463DE437296C35C2900F23292)]
可以看到这个就灭有疯狂执行
还有一个问题就是有一些动画是不能使用Tween这个东西设置它的曲线的
然后我们来试试
class _HYHomeScreenState extends State<HYHomeScreen> with SingleTickerProviderStateMixin {
// 创建AnimationController
AnimationController _controller;
// 我们需要在其他的地方使用 所以我们要让他变成全局变量
Animation _animation;
Animation _sizeAnimation;
Animation _colorAnima;
Animation _opacityAnima;
Animation _radiusAnima;
bool _isForward = true;
@override
void initState() {
// TODO: implement initState
super.initState();
// 1. 创建AnimationController
_controller = AnimationController(vsync: this, duration: Duration(seconds: 2));
// 2.设置Curve值
_animation = CurvedAnimation(parent: _controller, curve: Curves.linear);
// 3. Tween
// 这个东西返回的也是一个Animation 所以我们可以这样来赋值animation = 的方式来做
// 3.1 创建sizeTween
_sizeAnimation = Tween(begin: 10.0, end: 200.0).animate(_animation);
// 3.2 创建colorTween
_colorAnima = ColorTween(begin: Colors.orange, end: Colors.purple).animate(_animation);
// 3.3 创建opacityTween
_opacityAnima = Tween(begin: 0.0, end: 1.0).animate(_animation);
// 3.4 创建radiusTween
_radiusAnima = Tween(begin: 0.0, end: 2.0 * pi).animate(_animation);
// 监听动画的状态改变
_controller.addStatusListener((status) {
if( status == AnimationStatus.completed ) {
_controller.reverse();
// _isForward != _isForward;
} else if(status == AnimationStatus.dismissed) {
_controller.forward();
// _isForward != _isForward;
}
});
}
@override
Widget build(BuildContext context) {
print("执行 _HYHomeScreenState的build方法");
/**
* 1. 大小的变化
* 2. 颜色变化
* 3. 透明度
* 4. 旋转动画
*/
return Scaffold(
appBar: AppBar(
title: Text("title")
),
body: Center(
child: AnimatedBuilder(
animation: _animation,
builder: (ctx, child) {
return Opacity(
opacity: _opacityAnima.value,
child: Transform(
transform: Matrix4.rotationZ(_radiusAnima.value),
alignment: Alignment.center,
child: Container(
width: _sizeAnimation.value,
height: _sizeAnimation.value,
color: _colorAnima.value,
),
),
);
},
)
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.play_arrow),
onPressed: () {
if( _controller.isAnimating ) {
_controller.stop();
print(_controller.status);
} else {
// 然后在这里使用这个isForward
if(_controller.status == AnimationStatus.reverse) {
_controller.reverse();
} else if(_controller.status == AnimationStatus.forward) {
_controller.forward();
} else {
// 这里可能会有dismissed的情况 如果有问题我们可以在这里再对情况进行划分
_controller.forward();
}
}
},
),
);
}
@override
void dispose() {
// TODO: implement dispose
_controller.dispose();
super.dispose();
}
}
我们发现它是可以支持的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z94LahwO-1588228670373)(A00E5E2010084FCB8C2DF33BAAF2F258)]
但是有些是不行的
比如我们改成这个
// 2.设置Curve值
_animation = CurvedAnimation(parent: _controller, curve: Curves.elasticIn);
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aXs54kUL-1588228670375)(37102853555E4DAA859503BD7DC6B4AC)]
我们这种Curve就是不支持的 这个东西哪些支持哪些 不支持 官方也没有做过总结
所以如果不支持 我们就用controller就可以了
- 同时注意我们在分节的时候 命名文件需要 用下划线分割
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kGINezZU-1588228670376)(64D714A2B4A848B595ADDBFE6347AFC3)]
Hero 动画
首先做两个页面
import "package:flutter/material.dart";
main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "Flutter Demo",
theme: ThemeData(
primarySwatch: Colors.blue,
splashColor: Colors.transparent
),
home: HYHomeScreen(),
);
}
}
class HYHomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("title")
),
body: Center(
child: Text("hello")
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.pool),
onPressed: () {
},
),
);
}
}
modal_page
import "package:flutter/material.dart";
class HYModalPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Modal Page")
),
body: Text("Modal Page")
);
}
}
那么我们如果想要在main中跳转到ModalPage页面的话我们可以 这样做一个
class HYHomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("title")
),
body: Center(
child: Text("hello")
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.pool),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (ctx) {
return HYModalPage();
}
));
},
),
);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ksEmECIN-1588228670378)(D11F7E45A3D647AEAA7C85A5467F8865)]
在ios叫做push的方式
但是ios还有一种方式是从底部弹上来 那如果我们现在 写这个页面的时候我们不希望它从右边来弹出来
而是希望从底部弹出来 那我们怎么做呢 有一种比较简单的方式是直接在MaterialPageRoute然后设置它的属性
floatingActionButton: FloatingActionButton(
child: Icon(Icons.pool),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
fullscreenDialog: true,
builder: (ctx) {
return HYModalPage();
}
));
},
),
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JzY90Izf-1588228670380)(4EB22A93036348BDAEAAC9D620978639)]
然后这里按钮也改成了x
如果你觉得它不好看 我们可以自定义title
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wXD6eU9O-1588228670381)(947BB05589874088BB74457088AF0269)]
消失也是用pop来消失的
这个就是我们以modal的方式来弹出的 其实我们开发里面最常见的就是这两个
我们的MaterialRoute就是这两种
还有就是
Navigator.of(context).push(); 这个push里面是要一个Route 那我们传一个PageRouteBuilder
这个东西继承至 Route 这个东西可以设置一些动画相关的属性
Navigator.of(context).push(PageRouteBuilder(
));
然后我们来看看这个东西是怎么弹出的
class HYHomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("title")
),
body: Center(
child: Text("hello")
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.pool),
onPressed: () {
// Navigator.of(context).push(MaterialPageRoute(
// fullscreenDialog: true,
// builder: (ctx) {
// return HYModalPage();
// }
// ));
Navigator.of(context).push(PageRouteBuilder(
pageBuilder: (ctx, animation1, animation2) {
return HYModalPage();
}
));
},
),
);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NkdvoKXX-1588228670382)(F4CD7BCBE95646368B54A74A70809B70)]
可以看到它是瞬间切换的
它既不是从左边又不是从右边
这个时候我们可以给这个 页面嵌套一个Widget FadeTransition 这个东西可以给转场动画写东西了吗
Navigator.of(context).push(PageRouteBuilder(
pageBuilder: (ctx, animation1, animation2) {
return FadeTransition(
child: HYModalPage()
);
}
));
这个东西有opacity 我们可以给这个东西传入一个animation
虽然这里写的是一个opacity但是它要我们传的确实是一个 animation
这个animation可以在外面创建也可以直接使用这里的animation1 2
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dQPvk6Cg-1588228670384)(C99A16F3846342CD8E5D60756A947EC9)]
这个就可以把这个东西
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ArtdzYrP-1588228670385)(3C16AC8FE3AC486EADF3427439CE2D68)]
实现一个透明度的转场 渐变
如果你希望明显一点我们可以使用这个
transitionDuration: Duraction(second: 3)
Navigator.of(context).push(PageRouteBuilder(
transitionDuration: Duration(seconds: 3),
pageBuilder: (ctx, animation1, animation2) {
return FadeTransition(
opacity: animation1,
child: HYModalPage()
);
}
));
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-57LG5UWQ-1588228670387)(359D5F292D2A439994601A9E2C990438)]
我们最主要的就是做这里两个 这个东西是对路由的一个补充
但是这里还有一个问题就是 如果我们这里使用的是pushNamed的情况 我们怎么来加这个转场动画呢
onGenerateRoute
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ydd4I5MV-1588228670388)(BFF15DAA5721417CACB41992E878E019)]
- Hero动画
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HT6IExxO-1588228670391)(72865092F45C48DDA1614867999258B7)]
我们存在那种图片的选择的时候 点击一张图片 然后跳转到对应的图片上
我们希望能这样直接 转场
我们首先把图片展示出来
我们用这个网址来随机生成 图片 https://picsum.photos
https://picsum.photos/500/500?random=$index
import "package:flutter/material.dart";
import 'package:learn_flutter02/day12_Practice_animation/pages/modal_page.dart';
main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "Flutter Demo",
theme: ThemeData(
primarySwatch: Colors.blue,
splashColor: Colors.transparent
),
home: HYHomeScreen(),
);
}
}
class HYHomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("title")
),
body: Center(
child: GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 8,
childAspectRatio: 16/9,
mainAxisSpacing: 8,
),
children: List.generate(20, (index){
final imageURL = "https://picsum.photos/500/500?random=$index";
return Image.network(imageURL, fit: BoxFit.cover);
}),
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.pool),
onPressed: () {
// Navigator.of(context).push(MaterialPageRoute(
// fullscreenDialog: true,
// builder: (ctx) {
// return HYModalPage();
// }
// ));
Navigator.of(context).push(PageRouteBuilder(
transitionDuration: Duration(seconds: 3),
pageBuilder: (ctx, animation1, animation2) {
return FadeTransition(
opacity: animation1,
child: HYModalPage()
);
}
));
},
),
);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tg0JGt64-1588228670392)(1C3E818AD2744FCA8C6BD3FF823FD034)]
同样把展示图片的详情页做出来
然后做一个点击退出
import "package:flutter/material.dart";
class HYImageDetailPage extends StatelessWidget {
final String _imageURL;
HYImageDetailPage(this._imageURL);
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Center(
child: GestureDetector(
child: Hero(
child: Image.network(_imageURL)
),
onTap: () {
Navigator.of(context).pop();
}
),
)
);
}
}
然后做好 路由
import "package:flutter/material.dart";
import 'package:learn_flutter02/day12_Practice_animation/pages/image_detail.dart';
import 'package:learn_flutter02/day12_Practice_animation/pages/modal_page.dart';
main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "Flutter Demo",
theme: ThemeData(
primarySwatch: Colors.blue,
splashColor: Colors.transparent
),
home: HYHomeScreen(),
);
}
}
class HYHomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("title")
),
body: Center(
child: GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 8,
childAspectRatio: 16/9,
mainAxisSpacing: 8,
),
children: List.generate(20, (index){
final imageURL = "https://picsum.photos/500/500?random=$index";
return GestureDetector(
child: Image.network(imageURL, fit: BoxFit.cover),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (ctx) {
return HYImageDetailPage(imageURL);
}
));
}
);
}),
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.pool),
onPressed: () {
// Navigator.of(context).push(MaterialPageRoute(
// fullscreenDialog: true,
// builder: (ctx) {
// return HYModalPage();
// }
// ));
Navigator.of(context).push(PageRouteBuilder(
transitionDuration: Duration(seconds: 3),
pageBuilder: (ctx, animation1, animation2) {
return FadeTransition(
opacity: animation1,
child: HYModalPage()
);
}
));
},
),
);
}
}
但是这个时候我们的弹窗是这样的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qxnnhSXd-1588228670393)(D834E11A0D194F3BBA9ED9471C7ED5EE)]
这个时候我们就可以给这个图片嵌套一个Hero 然后设置一个唯一的tag 这个tag一定要唯一
然后在图片的位置也设置以一个这个东西 我们这里 tag就使用imageURL 作为tag
- 注意啊 这个tag一定要唯一不能乱写
import "package:flutter/material.dart";
import 'package:learn_flutter02/day12_Practice_animation/pages/image_detail.dart';
import 'package:learn_flutter02/day12_Practice_animation/pages/modal_page.dart';
main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "Flutter Demo",
theme: ThemeData(
primarySwatch: Colors.blue,
splashColor: Colors.transparent
),
home: HYHomeScreen(),
);
}
}
class HYHomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("title")
),
body: Center(
child: GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 8,
childAspectRatio: 16/9,
mainAxisSpacing: 8,
),
children: List.generate(20, (index){
final imageURL = "https://picsum.photos/500/500?random=$index";
return GestureDetector(
child: Hero(
tag: imageURL,
child: Image.network(imageURL, fit: BoxFit.cover)
),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (ctx) {
return HYImageDetailPage(imageURL);
}
));
}
);
}),
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.pool),
onPressed: () {
// Navigator.of(context).push(MaterialPageRoute(
// fullscreenDialog: true,
// builder: (ctx) {
// return HYModalPage();
// }
// ));
Navigator.of(context).push(PageRouteBuilder(
transitionDuration: Duration(seconds: 3),
pageBuilder: (ctx, animation1, animation2) {
return FadeTransition(
opacity: animation1,
child: HYModalPage()
);
}
));
},
),
);
}
}
import "package:flutter/material.dart";
class HYImageDetailPage extends StatelessWidget {
final String _imageURL;
HYImageDetailPage(this._imageURL);
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Center(
child: GestureDetector(
child: Hero(
tag: _imageURL,
child: Image.network(_imageURL)
),
onTap: () {
Navigator.of(context).pop();
}
),
)
);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-olkwAO3K-1588228670395)(47C800B0E92F4DAC8DBA75ECE39E5EC1)]
但是在上一个版本里面我们的 这个 MaterialPageRoute的方式不是渐变的 所以我们要使用
上一个版本使用这个MaterialPageRoute
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nyYH3XJm-1588228670396)(EE9F9D876F5140B1980A000B25816D9A)]
pageRouteBuilder的方式来使用这个
Navigator.of(context).push(PageRouteBuilder(
transitionDuration: Duration(seconds: 3),
pageBuilder: (ctx, animation1, animation2) {
return FadeTransition(
opacity: animation1,
child: HYModalPage()
);
}
));
要改成
class HYHomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("title")
),
body: Center(
child: GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 8,
childAspectRatio: 16/9,
mainAxisSpacing: 8,
),
children: List.generate(20, (index){
final imageURL = "https://picsum.photos/500/500?random=$index";
return GestureDetector(
child: Hero(
tag: imageURL,
child: Image.network(imageURL, fit: BoxFit.cover)
),
onTap: () {
Navigator.of(context).push(PageRouteBuilder(
pageBuilder: (ctx, anim1, anim2) {
return FadeTransition(
opacity: anim1,
child: HYImageDetailPage(imageURL)
);
}
));
}
);
}),
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.pool),
onPressed: () {
// Navigator.of(context).push(MaterialPageRoute(
// fullscreenDialog: true,
// builder: (ctx) {
// return HYModalPage();
// }
// ));
Navigator.of(context).push(PageRouteBuilder(
transitionDuration: Duration(seconds: 3),
pageBuilder: (ctx, animation1, animation2) {
return FadeTransition(
opacity: animation1,
child: HYModalPage()
);
}
));
},
),
);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q6xkgsTQ-1588228670398)(473A1E307C2147798215903D3158FA30)]