1.哪些类型动画
2.添加动画
3.为动画添加监听器
4.AnimatedWidget 与 AnimatedBuilder
5.Hero动画
1.哪些类型动画
大概分为两类:基于 tween 和基于物理。
tween 动画就是补间动画,定义开始点和结束点、时间线和定义转化时间和速度的曲线。
基于物理的动画:就是模拟真实世界。比如扔个球,会做抛物线,然后弹了几下。
2.添加动画
几个常见类:
Animation : 动画库核心,生成指导动画的值。
CurvedAnimation : Animation 子类,将过程抽象为一个非线性曲线。
AnimationController : Animation 子类,管理 Animation。
Tween : 在正在执行动画的对象所使用的数据范围之间生成值。比如红到绿,它可以自动生成过程。
动画的几个状态方法:
forward() 启动动画
reverse({double from}) 倒放动画
reset() 重置动画
stop({bool canceled = true}) 停止动画
动画使用大概步骤:
① 创建 AnimationController ,指定动画时间;
② 创建 Tween ,传入区间大小变化,传入控制器 AnimationController ;
③ 添加动画变化监听器,类型是 double 类型,每回调一次就会用 setState() 刷新一次页面;
④ 添加动画状态监听器,来监听动画的状态:开始和停止等,利用 setState() 刷新页面;
⑤ 添加一个图片,动态设置它的大小;
示例代码如下,大概就是一个图标从宽高为0两秒内变成宽高为300的过程:
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyApp>
with SingleTickerProviderStateMixin {
late Animation<double> animation;
late AnimationController controller;
//设置了一个初始状态。
late AnimationStatus animationStatus = AnimationStatus.reverse;
late double animationValue = 0;
@override
void initState() {
super.initState();
// ① 创建 AnimationController ,指定动画时间;
// 其中 vsync: this 表示该动画被别的页面遮挡,防止消耗不必要的资源。
controller =
AnimationController(duration: const Duration(seconds: 2), vsync: this);
// ② 创建 Tween ,传入区间大小变化,传入控制器 AnimationController ;
animation = Tween<double>(begin: 0, end: 300).animate(controller)
// ③ 添加动画变化监听器,类型是 double 类型,每回调一次就会用 setState() 刷新一次页面;
..addListener(() {
setState(() {
animationValue = animation.value;
});
})
// ④ 添加动画状态监听器,来监听动画的状态:开始和停止等,利用 setState() 刷新页面;
..addStatusListener((AnimationStatus status) {
setState(() {
animationStatus = status;
});
});
}
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(top: 100),
child: Column(
children: [
//点击监听
GestureDetector(
onTap: () {
//重置动画
controller.reset();
//动画开始
controller.forward();
},
child: Text(
'Start',
textDirection: TextDirection.ltr,
),
),
Text('State:' + animationStatus.toString(),
textDirection: TextDirection.ltr),
Text('Value:' + animationValue.toString(),
textDirection: TextDirection.ltr),
// ⑤ 添加一个图片,动态设置它的大小;
Container(
height: animation.value,
width: animation.value,
child: FlutterLogo(),
)
],
));
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
}
3.为动画添加监听器
上面代码中已经添加了两个监听:
..addListener 动画的值发生变化被调用
..addStatusListener 动画状态发生变化被调用
4.AnimatedWidget 与 AnimatedBuilder
上面代码中,必须得添加动画的变化监听,然后不停调用 setState ,才能显示动画。AnimatedWidget 可以简化这一个操作,AnimatedWidget 很常用,它的作用就是简化动画。
简化完成的代码:
import 'package:flutter/material.dart';
import 'package:flutter/animation.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyApp>
with SingleTickerProviderStateMixin {
late Animation<double> animation;
late AnimationController controller;
@override
void initState() {
super.initState();
// ① 创建 AnimationController ,指定动画时间; 其中 vsync: this 表示该动画被别的页面遮挡,防止消耗不必要的资源。
controller =
AnimationController(duration: const Duration(seconds: 2), vsync: this);
// ② 创建 Tween ,传入区间大小变化,传入控制器 AnimationController ;
animation = Tween<double>(begin: 0, end: 300).animate(controller);
controller.forward();
}
@override
Widget build(BuildContext context) {
return new AnimatedLogo(animation: animation);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
}
class AnimatedLogo extends AnimatedWidget {
AnimatedLogo({Key? key, required Animation<double> animation})
: super(key: key, listenable: animation);
@override
Widget build(BuildContext context) {
final Animation<double> animation = listenable as Animation<double>;
return new Center(
child: new Container(
margin: new EdgeInsets.symmetric(vertical: 10.0),
height: animation.value,
width: animation.value,
child: new FlutterLogo(),
)
);
}
}
AnimatedBuilder 是用于构建动画的通用 widget,作用是拆分动画,可以将动画和 widget 进行分离。
将上面的动画拆分为三块:显示图片,定义动画,渲染动画效果。
示例代码:
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyApp>
with SingleTickerProviderStateMixin {
late Animation<double> animation;
late AnimationController controller;
@override
void initState() {
super.initState();
// ① 创建 AnimationController ,指定动画时间; 其中 vsync: this 表示该动画被别的页面遮挡,防止消耗不必要的资源。
controller =
AnimationController(duration: const Duration(seconds: 2), vsync: this);
// ② 创建 Tween ,传入区间大小变化,传入控制器 AnimationController ;
animation = Tween<double>(begin: 0, end: 300).animate(controller);
controller.forward();
}
@override
Widget build(BuildContext context) {
return GrowTransition(child: LogoWidget(),animation: animation);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
}
class GrowTransition extends StatelessWidget {
GrowTransition({required this.child, required this.animation});
final Widget child;
final Animation<double> animation;
@override
Widget build(BuildContext context){
return Center(
child: AnimatedBuilder(
animation: animation,
builder: (context, child) {
return Container(
height: animation.value,
width: animation.value,
child: child,
);
},
child: child,
),
);
}
}
class LogoWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Container(
margin: EdgeInsets.symmetric(vertical: 10),
child: FlutterLogo(),
);
}
}
5.Hero动画
从第一个页面跳转到第二个页面,并且两个页面都有这个图标,可以用这个动画来实现。实现一个飞入的感觉。
效果可以看官网:Flutter Hero动画 - Flutter中文网
示例代码,下面代码我把 timeDilation = 5.0; 注释了,加上这个报错,不知道为什么,报错信息是
Error: Setter not found: 'timeDilation'.
timeDilation = 5.0;
有知道的小伙伴告诉我一下原因吧~
下面是完整代码:
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart' show timeDilation;
void main() {
runApp(MaterialApp(home: HeroAnimation()));
}
class PhotoHero extends StatelessWidget {
const PhotoHero({ Key? key,required this.photo,required this.onTap, required this.width }) : super(key: key);
final String photo;
final VoidCallback onTap;
final double width;
Widget build(BuildContext context) {
return new SizedBox(
width: width,
child: new Hero(
tag: photo,
child: new Material(
color: Colors.transparent,
child: new InkWell(
onTap: onTap,
child: new Image.asset(
photo,
fit: BoxFit.contain,
),
),
),
),
);
}
}
class HeroAnimation extends StatelessWidget {
@override
Widget build(BuildContext context) {
// timeDilation = 5.0;
return new Scaffold(
appBar: new AppBar(
title: const Text('Basic Hero Animation'),
),
body: new Center(
child: new PhotoHero(
photo: 'assets/images/my_icon.jpg',
width: 300.0,
onTap: () {
Navigator.of(context).push(new MaterialPageRoute<void>(
builder: (BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: const Text('Flippers Page'),
),
body: new Container(
// The blue background emphasizes that it's a new route.
color: Colors.lightBlueAccent,
padding: const EdgeInsets.all(16.0),
alignment: Alignment.topLeft,
child: new PhotoHero(
photo: 'assets/images/my_icon.jpg',
width: 100.0,
onTap: () {
Navigator.of(context).pop();
},
),
),
);
}
));
},
),
),
);
}
}