Flutter动画分类
flutter动画 可以分为补间动画和基于物理动画
- 补间动画 :在补间动画中,定义了开始点和结束点,时间线和定义转换的时间和速度曲线,然后由框架计算出如何从开始点到结束点
- 基于物理动画: 在基于物理动画中,运动模拟为与真实世界的行为相似,例如:球体落地过程中其速度,重力都会影响其执行
Flutter中动画的基础类
- Animation:是flutter动画库中的核心类,它生成知道动画的值
- CurvedAnimation:Animation的子类,将动画抽象成为一个非线性的曲线
- AnimationController:Animation的子类,用来管理Animation
- Tween:在正在执行的动画对象所使用的数值范围之间生成值.
Animation
在Flutter中,animation对象本身和UI渲染没有任何关系,Animation是个抽象类,它拥有动画的当前值和状态(进行中,停止等),其中比较常用的Animation类是Animation< double>. Flutter中Animation对象是一个在一段时间内依次生成一个区间值的类,Animation对象输出可以是线性的,曲线的,一个步进函数或者在任何可以设计的映射.根据Animation对象的控制方式,动画可以反向进行,甚至可以在中间切换方向.
- Animation还可以生成除了double以外的其他类型值,例如:Animation< Color>,Animation< Size>等
- Animation对象有状态,可以通过访问其value属性,获取动画的当前值;
- Animation对象本身和UI渲染没有任何关系;
CurvedAnimation
CurvedAnimation将动画过程定义为一个非线性曲线(跟Android动画中差值器似得)
CurvedAnimation animation=CurvedAnimation(curve: Curves.linear, parent: controller);
复制代码
Curves是系统内置的一些差值器,也可以自定义
class SelfCurve extends Curve{
@override
double transform(double t) {
return sin(t * pi * 2);
}
}
复制代码
AnimationController
- AnimationController 是一个特殊的Animation对象,在屏幕刷新的每一帧,就会生成一个新的值,默认情况下,AnimationController在给定的时间内会线性的生成从0.0~1.0的数字
- AnimationController派生自Animation< double> ,因此可以在需要Animation对象的任何地方使用,
- 当创建一个AnimationController对象时需要传一个vsync参数,该值可以在动画不需要显示时及时释放,例如退到后台,跳转到另一个页面等
//需要state 扩展 with SingleTickerProviderStateMixin
AnimationController controller=AnimationController(duration:
Duration(milliseconds: 200),vsync: this);
controller.forward();//启动动画
controller.stop();//停止动画
controller.reset();//重置动画
controller.reverse();//倒放动画
复制代码
Tween
在默认情况下,AnimationController的值是从0.0~1.0,如果需要改变该值的范围则需要使用Tween
- Tween是一个无状态(stateless)对象,需要begin和end值。Tween的唯一职责就是定义从输入范围到输出范围的映射。输入范围通常为0.0到1.0,但这不是必须的
- Tween继承自Animatable,而不是继承自Animation。Animatable与Animation相似,不是必须输出double值。例如,ColorTween指定两种颜色之间的过渡。
- Tween对象不存储任何状态。相反,它提供了evaluate(Animation animation)方法将映射函数应用于动画当前值。 Animation对象的当前值可以通过value()方法取到。evaluate函数还执行一些其它处理,例如分别确保在动画值为0.0和1.0时返回开始和结束状态。 建了一个控制器、一条曲线和一个Tween:
final AnimationController controller = new AnimationController(
duration: const Duration(milliseconds: 500), vsync: this);
final Animation curve =
new CurvedAnimation(parent: controller, curve: Curves.easeOut);
Animation<int> alpha = new IntTween(begin: 0, end: 255).animate(curve);
复制代码
综上所述,使用Animation时,先定义AnimationController,如果需要的动画不是线性的,则需要为其设置CurvedAnimation作为差值器,如果值不是从0.0~1.0则需要设置Tween为其设置值的变化范围
为widget添加动画
使用AnimationController构建动画
class AnimationPage extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return AnimationState();
}
}
class AnimationState extends State<AnimationPage>
with SingleTickerProviderStateMixin {
AnimationController controller;
Animation<double> animation;
double animationValue;
AnimationStatus animationState;
@override
void initState() {
super.initState();
//创建动画
controller = AnimationController(
duration: Duration(milliseconds: 2000), vsync: this);
//设置值的区间
animation = Tween<double>(begin: 0, end: 300).animate(controller)
..addListener(() {
//每次动画的值改变时回调该方法
setState(() {
animationValue = animation.value;
});
})
..addStatusListener((status) {
//动画状态改变时回调该方法
setState(() {
animationState = status;
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter动画'),
),
body: Center(
child: Column(
children: [
RaisedButton(
onPressed: () {
controller.reset();
controller.forward();
},
child: Text('点击开始动画'),
),
Text("$animationValue"),
Text("$animationState"),
Container(
height: animation.value,
width: animation.value,
child: FlutterLogo(),
)
],
)),
);
}
@override
void dispose() {
//回收动画
controller.dispose();
super.dispose();
}
}
复制代码
使用AnimationWidget给Widget添加动画
AnimationWidget,实际上就是对需要执行动画变化的widget的一个封装,不用在手动调用setState()方法进行widget重绘
- 首先将需要执行动画的widget封装到AnimationWidget中
class AnimationLogo extends AnimatedWidget {
AnimationLogo({Key key, @required Animation<double> animation})
: super(key: key, listenable: animation);
@override
Widget build(BuildContext context) {
Animation<double> animation = listenable;
return Container(
height: animation.value,
width: animation.value,
child: FlutterLogo(),
);
}
}
复制代码
- 将原来设置动画的地方直接换成封装好的Widget即可,并且将动画传给封装好的widget
AnimationLogo(animation: animation)
复制代码
使用AnimationBuilder为widget构建动画
- AnimatedBuilder继承AnimatedWidget
- animation参数不为空,传入一个动画,然后传给AnimatedWidget的listenable,listenable在AnimatedWidget已介绍过,用于监听该动画,然后通知更新UI,就无需手动调用addListener监听动画然后调用setState更新UI
- build参数不为空,传入一个build方法自定义动画起作用的控件
- child是可选参数,有传的话,可以用于包裹在build方法返回的控件里面,作用可以详细看源码里child参数的说明,child是动画不起作用的子控件树,child由外部传入的话在每次动画更新重绘的时候就无需重绘该子控件树,可以提高效率
class AnimationPage extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return AnimationState();
}
}
class AnimationState extends State<AnimationPage>
with SingleTickerProviderStateMixin {
AnimationController controller;
Animation<double> _animation;
// double animationValue;
// AnimationStatus animationState;
@override
void initState() {
super.initState();
controller = AnimationController(
duration: Duration(milliseconds: 2000), vsync: this);
_animation = Tween<double>(begin: 0, end: 300).animate(controller);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter动画'),
),
body: Center(
child: Column(
children: [
RaisedButton(
onPressed: () {
controller.reset();
controller.forward();
},
child: Text('点击开始动画'),
),
AnimatedBuilder(animation: _animation,
child: FlutterLogo(),
builder: (context,child)=>Container(
width: _animation.value,
height: _animation.value,
child: child,
)),
],
)),
);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
}
复制代码
AnimationBuilder其中的build 会在每次动画值更新时调用setState()进行重新绘制,如果给child参数赋值并将其赋值给build中,则每次重新绘制时则不再重新绘制child部分,以提高效率和节省资源
Hero动画
在 Flutter中可以用 Hero widget创建这个动画。当 hero 通过动画从源页面飞到目标页面时,目标页面逐渐淡入视野。通常, hero 是用户界面的一小部分,如图片,它通常在两个页面都有.跟Android中的共享元素差不多,就是让用户在交互上非常好的衔接效果.使用起来更加连贯
Hero 源码
const Hero({
Key key,
@required this.tag,
this.createRectTween,
this.flightShuttleBuilder,
this.placeholderBuilder,
this.transitionOnUserGestures = false,
@required this.child,
}) : super(key: key);
复制代码
- tag:必要参数,用于关联两个Hero动画的标识
- child:必要参数,定义动画所呈现的widget
- createRectTween:可选参数,定义目标Hero的边界,在从起始位置到目的位置的“飞行”过程中该如何变化;
Hero标准实现
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart' show timeDilation;
class HeroAnimation extends StatelessWidget {
const HeroAnimation({Key key, this.photo, this.onTap, this.width})
: super(key: key);
final String photo;
final VoidCallback onTap;
final double width;
@override
Widget build(BuildContext context) {
return SizedBox(
width: width,
child: Hero(//定义hero
tag: photo,//定义tag,为图片的url
child: GestureDetector(
onTap: onTap,
child: Image.network(
photo,
fit: BoxFit.contain,
),
),
),
);
}
}
class HeroPage extends StatelessWidget{
static const String url='https://sf3-ttcdn-tos.pstatp.com/img/user-avatar/96119b38be4be1dafc95fcbff0d9ee30~300x300.image';
@override
Widget build(BuildContext context) {
timeDilation = 5.0;
return Scaffold(
appBar: AppBar(title: Text('测试hero动画'),),
body: Center(
//使用Hero
child: HeroAnimation(photo: url,width: 300,onTap: (){
Navigator.of(context).push(MaterialPageRoute(builder: (context){
return Scaffold(
appBar: AppBar(title: Text('测试hero第二个页面'),),
body: Container(
padding: EdgeInsets.all(10),
color: Colors.lightGreen,
alignment: Alignment.topCenter,
//第二个页面也使用该Hero的widget
child: HeroAnimation(photo: url,width: 100,onTap: (){
Navigator.of(context).pop();
},),
),
);
}));
},),
),
);
}
}
复制代码