Flutter中的动画类型
补间动画(tween):在补间动画中,定义了开始点和结束点、时间线以及定义转换时间和速度的曲线,然后由框架计算如何从开始点过渡到结束点。
基于物理的动画:在基于物理的动画中,运动被模拟为与真实世界的行为相似。例如 当你掷球时,他在何处落地,取决于抛球的速度,球的重量。距离地面的距离,
动画相关的类
Animation:是flutter动画库中的一个核心类,它生成指导动画的值
CurvedAnimation:Animation的一个子类,将过程抽象为一个非线性曲线
AnimationController:Animation的子类,用来管理Animation
Tween:在正在执行动画的对象所使用的数据范围之间生成值,例如从颜色值 0到255
Animation
flutter中的animation对象**是一个在一段时间内依次生成一个区间之间的值的类** ,animation对象的输出可以是线性、曲线、一个步进函数或者任何其他可以设计的映射。根据animation对象的控制方式,动画可以方向运行,甚至可以在中间切换方法
-Animation还可以生成出了double之外的其他类型值,如 Animation<Color> ,Animation(Size)
-Animation对象是有状态的,可以通过访问value属性获取动画的当前值
-Animation对象本身和UI渲染无关
#CurvedAnimation
CurvedAnimation将动画过程定义为一个非线性曲线
final CurvedAnimation curve = CurvedAnimation(parent:controller,curve:Curves.easeIn);
自定义曲线 ,继承 Curve 重写 transform 方法
```dart
class ShakeCure extends Curve{
@override
double transform(double t){
return math.sin(t * math.PI *2 );
}
}
AnimationController
AnimationController是一个特殊的Animation对象,在屏幕刷新的每一帧,就会生成一个新的值。默认情况下,AnimationController在给定的时间段内会线性的生成从0.0到1.0的数字,例如:
final AnimationController controller = AnimationController(duration:Duration(milliseconds:2000),vsync:this)
此时动画就会在2000毫秒内,从0.0的值过渡到1.0
AnimationController继承自Animation<double>,因此可以在需要Animation对象的任何地方使用,但是AnimationController具有控制动画的其他方式
- forward() : 启动动画
- reverse({double from}):倒放动画
- reset() : 重置动画,
- stop({bool canceled = true}) : 定制动画
vsync参数是为了防止屏幕外动画消耗不必要的资源,可以将stateful 对象作为vsync的值
*在某些情况下,值(position,值动画的当前值)可能会超出AnimationController的0.0-1.0 范围,例如 fling()函数允许提供速度(velocity)、力量(force)、position(通过force对象)。位置(position)可以是任何东西,因此可以在范围之外,CuredAnimation生成的值也可以超过这个范围。根据选择的曲线,CuredAnimation的输出可以具有比喻输入更大的范围。例如 Curves.elasticln等弹性曲线会生成大于或小于默认范围的值*
Tween
默认情况下AnimationController对象额范围从0.0-1.0,如果需要不同的范围或者不同的数据类型,就可以使用Tween来配置动画
final Tween doubleTween = Tween<double>(begin:-100.0,end:0.0);
Tween是一个无状态的对象,需要begin和end值。Tween的唯一职责就是定义从输入返回到输出返回的映射。输入返回通常为0.0到1.0,但是不是必须的
Tween继承自Animatable<T> ,而不是继承自Animation<T>。Animatable与Animation相似,不是必须有输出的double值,例如 ColorTween知道两种颜色之间过渡
Tween colorTween = Tween(begin:Colors.transparent,end:Colors.black54)
以下构建一个控制器,一条曲线和一个tween:
AnimationController controller = AnimationController(duration:Duration(milliseconds:500),vsync:this)
Animation curve = CurvedAnimation(parent:controller,curve:Curves.easeOut);
Animation<int> alpha = IntTween(begin:0,end:255).animate(curve)
动画的监听
addListener() : 动画的值发生变化时会被调用
addStatusListener(AnimationStatus status):动画状态发生变化时被调用
class AnimationWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return AnimationState();
}
}
class AnimationState extends State<AnimationWidget>
with SingleTickerProviderStateMixin {
Animation<double> animation;
AnimationController controller;
AnimationStatus animationStatus;
double animationValue;
@override
void initState() {
super.initState();
//初始化动画
controller =
AnimationController(duration: Duration(seconds: 2), vsync: this);
animation = Tween<double>(begin: 0, end: 300).animate(controller)
//监听动画的执行过程
..addListener(() => setState(() => animationValue = animation.value))
//监听动画的执行状态
..addStatusListener((status) => setState(() => animationStatus = status));
}
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(top: 150),
child: Column(
children: <Widget>[
//点击时播放动画
GestureDetector(
onTap: () {
//重置动画
controller.reset();
//再次播放动画
controller.forward();
},
child: FlatButton(
child: Text("Start Animation"),
),
),
//显示动画执行状态
Text("animationState = $animationStatus"),
//显示动画执行中数值的变化
Text("animationValue = $animationValue"),
Container(
height: animationValue,
width: animationValue,
child: FlutterLogo(),
)
],
),
);
}
}
以上代码执行一个简单的动画,通过动画改变logo的宽高
9xhmw-acjtk.gif

AnimationWidget
AnimatedWidget 可以理解为Animation的助手,可以简化对动画的使用
//创建一个继承自AnimatedWidget的子类,
class LogoAnimationWidget extends AnimatedWidget {
//重写构造函数,从外部创建一个动画进来
LogoAnimationWidget({Key key, Animation<double> animation})
: super(key: key, listenable: animation);
@override
Widget build(BuildContext context) {
final Animation<double> animation = this.listenable;
return Container(
width: animation.value,
height: animation.value,
child: FlutterLogo(),
);
}
@override
void _handleChange() {
setState(() {
// The listenable's state is our build state, and it changed already.
});
}
}
//使用时就像不同widget那样,
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(top: 150),
child: Column(
children: <Widget>[
//点击时播放动画
FlatButton(
child: Text("Start Animation"),
onPressed: () {
//重置动画
controller.reset();
//再次播放动画
controller.forward();
},
),
LogoAnimationWidget(
animation: animation,
)
],
),
);
}
AnimatedBuilder
是用于构建动画的通用widget,将动画和widget进行分离。
在上面的实例中存在的一个问题:更改动画需要更改显示logo的widget。解决方案就是将职责分离
- 显示logo
- 定义 animation对象
- 渲染过渡效果
class LogoWidget extends StatelessWidget {
const LogoWidget();
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.symmetric(vertical: 10),
child: FlutterLogo(),
);
}
}
class GrowTransition extends StatelessWidget {
final Widget child;
final Animation<double> animation;
GrowTransition({this.animation, this.child = const LogoWidget()});
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: animation,
builder: ((context, widget) => Container(
height: animation.value,
width: animation.value,
child: child,
)),
);
}
}
Hero动画
两个页面之间的过渡动画,个人感觉就是元素共享动画
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart' show timeDilation;
class HeroWidget extends StatelessWidget {
final String photo;
final VoidCallback onTap;
final double width;
final double height;
HeroWidget({this.photo, this.onTap, this.width, this.height});
@override
Widget build(BuildContext context) {
return SizedBox(
width: width,
child: Hero(
tag: photo,
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
child: Image.network(
photo,
),
),
),
),
);
}
}
class HeroAnimation extends StatelessWidget {
@override
Widget build(BuildContext context) {
//设置动画速度
timeDilation = 3.0;
return Scaffold(
appBar: AppBar(
title: Text("HeroAnimation"),
),
body: Container(
alignment: Alignment.center,
child: HeroWidget(
photo:
"https://c-ssl.duitang.com/uploads/item/201501/18/20150118135140_84XVt.thumb.700_0.jpeg",
width: 300,
height: 300,
onTap: () {
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
return Scaffold(
appBar: AppBar(
title: Text("过渡page"),
),
body: Container(
color: Colors.lightBlueAccent,
padding: EdgeInsets.all(20),
alignment: Alignment.center,
child: HeroWidget(
photo:
"https://c-ssl.duitang.com/uploads/item/201501/18/20150118135140_84XVt.thumb.700_0.jpeg",
width: 100,
height: 100,
onTap: () => Navigator.pop(context),
),
),
);
}));
},
),
),
);
}
}