所有代码都已经上传至github,如果觉得有用,麻烦给个star, 谢谢。
介绍
-
常用类介绍
-
Tween
补间动画,可以定义开始点和结束点,动画执行时会根据开始点与结束点给的范围进行取值,每一帧取一个值,可以用于绘制新视图,一帧帧的绘制行程了动画效果。
Tween<T>(begin: begin, end: end)begin与end 类型由Tween传入的类型决定。







-
Animation
这是一个用于管理动画状态与生成动画每一帧需要的值,在这个类中,可以设置动画每一帧的监听与动画状态的监听。
-
CurvedAnimation 曲线动画,它继承与Animation,可用于实现带有曲线模型的动画,点击Curves可以查看更多
-
AnimationController
动画控制器,用于管理Animation,使用AnimationController 需要传入vsync,通常是通过使用AnimationController的类混入SingleTickerProviderStateMixin,把当前this传入就可以,当一个类有多个AnimationController时也混入TickerProviderStateMixin。还需要传入一个duration,用于控制动画执行时间。
-
AnimatedWidget 动画部件,用于把有动画的视图与主视图隔离开来,当动画绘制时不会引起主视图跟随重新绘制,这是一个抽象类,通常是通过AnimatedWidget的派生类来实现动画隔离。
-
AnimatedBuilder
继承于AnimatedWidget,更便于使用。
-
-
动画添加监听器
动画添加监听器就是对Animation操作。
-
addListener
可以监听每一帧
animation.addListener(() { //打印每帧的值 print(animation.value); }); -
addStatusListener
用于监听Animation的状态
animation.addStatusListener((AnimationStatus status) { if (status == AnimationStatus.completed) { //动画完成 } else if (status == AnimationStatus.dismissed) { //动画取消 } else if (status == AnimationStatus.reverse) { //动画反向 } else if (status == AnimationStatus.forward) { //动画向前(开始) } });
-
-
动画状态介绍
- dismissed 动画取消
- forward 动画向前,通常调用次方法开始动画。
- reverse 动画反向
- completed 动画完成
-
动画控制 动画控制就是AnimationController来执行动画。
- forward 动画向前,可以用于启动动画。
- reverse 动画反向,可以用于启动动画。
- reset 重置动画
- stop 停止动画
- repeat 重复动画,可以用于启动动画。
-
动画实践方式
- Tween 单独使用 Tween 单独使用就是对Animation设置一个addListener,捕获到每帧的回调在来重新绘制视图
import 'package:flutter/material.dart'; ///位移 class DisplacementPage extends StatefulWidget { @override DisplacementPageState createState() => DisplacementPageState(); } class DisplacementPageState extends State<DisplacementPage> with SingleTickerProviderStateMixin { AnimationController controller; Animation<Offset> animation; @override void initState() { super.initState(); controller = AnimationController(vsync: this, duration: Duration(milliseconds: 1000)); Offset begin = Offset(0.0, 0.0); Offset end = Offset(0.0, 200); animation = Tween<Offset>(begin: begin, end: end).animate(controller) ..addListener(() { setState(() {}); }) ..addStatusListener((AnimationStatus status) { if (status == AnimationStatus.completed) { controller.reverse(); } else if (status == AnimationStatus.dismissed) { controller.forward(); } }); controller.forward(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('位移'), centerTitle: true, ), body: Align( child: Container( width: 100, height: 100, margin: EdgeInsets.only(top: animation.value.dy), color: Colors.green, ), ), ); } @override void dispose() { controller.dispose(); super.dispose(); } }-
AnimatedBuilder 使用
AnimateBuilder继承与AnimatedWidget,需要传入一个AnimationController与builder, builder是一个TransitionBuilder虚拟类型,它真实类型是一个Widget
typedef TransitionBuilder = Widget Function(BuildContext context, Widget child);为配合AnimatedBuilder展示动画效果,系统还为我们提供了Transform,Transform其实就是继承了 SingleChildRenderObjectWidget,由Transform构建下面三种方式使用:
- Transform.rotate
旋转变换,需要传入一个Widget与一个angle,angle是角度值,0°值是0,360°值是2π π通常使用 math.pi,使用这个时需要引入import 'dart:math' as math; - Transform.translate
移动变换,需要传入一个Widget与offset - Transform.scale
缩放变换,需要传入一个Widget与scale,scale的值是相对自身大小的倍数,例如
scale:1.2 放大到自身的1.2倍大
import 'package:flutter/material.dart'; import 'package:flutter_animation/util.dart'; import 'dart:math' as math; ///AnimatedBuilder class AnimatedBuilderPage extends StatefulWidget { @override AnimatedBuilderPageState createState() => AnimatedBuilderPageState(); } class AnimatedBuilderPageState extends State<AnimatedBuilderPage> with SingleTickerProviderStateMixin { AnimationController controller; //旋转 Animation<double> rotate; //移动 Animation<Offset> translate; //缩放 Animation<double> scale; @override void initState() { super.initState(); controller = AnimationController(vsync: this, duration: Duration(milliseconds: 2000)); CurvedAnimation curvedAnimation = CurvedAnimation(parent: controller, curve: Curves.linear); rotate = Tween<double>(begin: 0, end: math.pi * 2).animate(curvedAnimation); translate = Tween<Offset>(begin: Offset(0, 0), end: Offset(300, 50)).animate(curvedAnimation); scale = Tween<double>(begin: 1, end: 2).animate(curvedAnimation); controller.repeat(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('AnimatedBuilder'), centerTitle: true, ), body: Align( child: Column( children: <Widget>[ titleBarWidget( title: 'AnimatedBuilder就是把动画视图跟主页面分离开了,动画执行时调用的setState只会重新绘制AnimatedBuilder中的视图,不会对主页面重新绘制' '\nAnimatedBuilder的builder需要返回一个TransitionBuilder,' '\n构建TransitionBuilder时可以有三种动画,' '\nTransform.rotate 旋转' '\nTransform.translate 移动' '\nTransform.scale 缩放'), sizedBox, AnimatedBuilder( animation: controller, builder: (context, child) { return Transform.rotate( angle: rotate.value, child: Container( width: 100, height: 100, color: Colors.green, child: Icon(Icons.android, color: Colors.white), ), ); }, ), sizedBox, AnimatedBuilder( animation: controller, builder: (context, child) { return Transform.translate( offset: translate.value, child: Container( width: 100, height: 100, color: Colors.green, child: Icon(Icons.android, color: Colors.white), ), ); }, ), sizedBox, AnimatedBuilder( animation: controller, builder: (context, child) { return Transform.scale( scale: scale.value, child: Container( width: 100, height: 100, color: Colors.green, child: Icon(Icons.android, color: Colors.white), ), ); }, ), ], ), ), ); } @override void dispose() { controller.dispose(); super.dispose(); } }
注意: Transform的变化是在应用绘制阶段,Transform中的Widget在布局阶段就确 定了大小与位置,在Transform动画变化时都不会对其他的视图造成影响。
- Transform.rotate
-
Transition使用 Transition是一种继承AnimatedWidget实现动画的一种总称,它有下面方式可以使用:
- SlideTransition 位移 过渡
- ScaleTransition 缩放 过渡
- RotationTransition 旋转 过渡
- SizeTransition 大小 过渡
- FadeTransition 渐变 过渡
- PositionedTransition 定位 过渡
- RelativePositionedTransition
相对定位 过渡 - DecoratedBoxTransition 装饰 过渡
- AlignTransition 对齐 过渡
- DefaultTextStyleTransition 默认字体样式 过渡

import 'package:flutter/material.dart'; import 'package:flutter_animation/util.dart'; ///Transition class TransitionPage extends StatefulWidget { @override TransitionPageState createState() => new TransitionPageState(); } class TransitionPageState extends State<TransitionPage> with SingleTickerProviderStateMixin { AnimationController controller; //旋转 Animation<double> rotate; //移动 Animation<Offset> translate; //缩放 Animation<double> scale; @override void initState() { super.initState(); controller = AnimationController(vsync: this, duration: Duration(milliseconds: 2000)); //加入曲线模型 CurvedAnimation curvedAnimation = CurvedAnimation(parent: controller, curve: Curves.linear); rotate = Tween<double>(begin: 0, end: 1).animate(curvedAnimation); translate = Tween<Offset>(begin: Offset(0, 0), end: Offset(1.5, 1.5)).animate(curvedAnimation); scale = Tween<double>(begin: 1, end: 2).animate(curvedAnimation); controller.repeat(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Transition'), centerTitle: true, ), body: Align( child: Column( children: <Widget>[ titleBarWidget(title: 'Transition 继承AnimatedWidget'), sizedBox, RotationTransition( turns: rotate, child: Container( width: 100, height: 100, color: Colors.green, child: Icon(Icons.android, color: Colors.white), ), ), sizedBox, SlideTransition( position: translate, child: Container( width: 100, height: 100, color: Colors.green, child: Icon(Icons.android, color: Colors.white), ), ), sizedBox, ScaleTransition( scale: scale, child: Container( width: 100, height: 100, color: Colors.green, child: Icon(Icons.android, color: Colors.white), ), ), ], ), ), ); } @override void dispose() { controller.dispose(); super.dispose(); } }注意:这些的构造函数里面需要传入的值都是相对自身大小、角度、位置、坐标倍数来设置的,具体可以查看对应类里的build方法
动画类型
-
补间动画
-
介绍
Tween补间动画就如上面介绍的那样,在创建Tween时,还可以直接使用下面类型来创建Tween,下面类型都是继承于Tween,只是在Tween基础上多了一步lerp处理
-
类型
- ReverseTween 反向
- ColorTween 颜色
- SizeTween 大小
- RectTween 矩形
- IntTween 整型
- StepTween 渐变
- ConstantTween 常数
- CurveTween 曲线
-
-
基于物理动画
-
介绍
基于物理动画就是模拟日常生活中的一些想象,例如球落地、橡皮筋拉起松开效果等等。
-
类型
类型太多了,具体可以查看Curves
-
使用
AnimationController controller; //缩放 Animation<double> scale; @override void initState() { super.initState(); controller = AnimationController(vsync: this, duration: Duration(milliseconds: 2000)); //加入曲线模型 CurvedAnimation curvedAnimation = CurvedAnimation(parent: controller, curve: Curves.linear); scale = Tween<double>(begin: 1, end: 2).animate(curvedAnimation); controller.repeat(); }
-
常用动画
-
动画列表
AnimatedList是对ListView中的item执行动画效果,可以根据需要传入Tween 类型的动画,使用代码如下:

///列表动画
class AnimatedListPage extends StatefulWidget {
@override
AnimatedListPageState createState() => AnimatedListPageState();
}
class AnimatedListPageState extends State<AnimatedListPage> {
final GlobalKey<AnimatedListState> globalKey = GlobalKey<AnimatedListState>();
int index = 5;
int type = 1;
///item视图
Widget itemWidget({context, animation, index}) {
Widget child = Container(
width: double.infinity,
height: 100,
margin: EdgeInsets.only(left: 16, top: 16, right: 16),
color: Colors.primaries[index],
);
Widget size = SizeTransition(sizeFactor: animation, child: child);
Widget scale = ScaleTransition(scale: animation, child: child);
Widget fade = FadeTransition(opacity: animation, child: child);
switch (type) {
case 1:
return size;
case 2:
return scale;
case 3:
return fade;
default:
return size;
}
}
///构建 视图
Widget buildItemWidget(context, index, animation) {
return itemWidget(context: context, index: index, animation: animation);
}
///删除视图
Widget removeItemBuilder(BuildContext context, Animation<double> animation) {
return itemWidget(context: context, index: index, animation: animation);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('AnimatedList'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.add_circle),
onPressed: () {
index += 1;
globalKey.currentState.insertItem(index - 1);
},
),
IconButton(
icon: Icon(Icons.remove_circle),
onPressed: () {
if (index > 1) {
index -= 1;
globalKey.currentState.removeItem(index, removeItemBuilder);
}
},
),
PopupMenuButton(
icon: Icon(Icons.more_vert),
itemBuilder: (context) {
return [
PopupMenuItem(
child: Text('展开收起'),
value: 1,
),
PopupMenuItem(
child: Text('放大缩小'),
value: 2,
),
PopupMenuItem(
child: Text('渐变'),
value: 3,
),
];
},
onSelected: (value) {
type = value;
setState(() {});
},
),
],
),
body: AnimatedList(
key: globalKey,
itemBuilder: buildItemWidget,
initialItemCount: index,
),
);
}
}
-
共享元素
Hero是共享元素必须的控件,系统会根据相同Tag的Hero执行动画效果,代码如下:

import 'package:flutter/material.dart';
import 'package:flutter_animation/util.dart';
///共享元素控件
class PhotoWidget extends StatelessWidget {
final double width;
final double height;
final String path;
PhotoWidget({this.width = double.infinity, this.height, this.path});
@override
Widget build(BuildContext context) {
return SizedBox(
width: width,
child: Hero(
tag: path,
child: Image.asset(
path,
fit: BoxFit.contain,
package: 'flutter_animation',
),
),
);
}
}
///共享元素
class HeroAnimationPage extends StatefulWidget {
@override
HeroAnimationPageState createState() => new HeroAnimationPageState();
}
class HeroAnimationPageState extends State<HeroAnimationPage> {
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('共享元素'),
centerTitle: true,
),
body: Align(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
titleBarWidget(title: 'Flutter 动画分为补间动画(Tween)与物理动画'),
sizedBox,
PhotoWidget(
width: 300,
height: 100,
path: 'assets/images/photo.png',
),
sizedBox,
buttonWidget(
title: '查看详情',
onPressed: () {
toPage(context, PhotoDetail());
},
),
],
),
),
),
);
}
void toPage(BuildContext context, Widget widget) {
Navigator.push(context, MaterialPageRoute(builder: (context) => widget));
}
}
///详情页面
class PhotoDetail extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('共享元素-详情'),
centerTitle: true,
),
body: Column(
children: <Widget>[
PhotoWidget(
width: double.infinity,
height: 300,
path: 'assets/images/photo.png',
),
Padding(
padding: EdgeInsets.all(16),
child: Text('\t\t\t\t蒙奇·D·路飞,日本漫画《航海王》的主角,外号“草帽”路飞,草帽海贼团、草帽大船团船长,极恶的世代之一。'
'橡胶果实能力者,悬赏金15亿贝里。梦想是找到传说中的One Piece,成为海贼王。'
'\n\n\t\t\t\t路飞性格积极乐观,爱憎分明,而且十分重视伙伴,不甘屈居于他人之下,对任何危险的事物都超感兴趣。'
'和其他传统的海贼所不同的是,他并不会为了追求财富而杀戮,而是享受着身为海贼的冒险和自由。'),
),
],
),
);
}
}
-
交错动画
交错动画就是多个动画组合使用,使用时通过Interval来控制动画执行时间区间。代码如下:

import 'package:flutter/material.dart';
///交错动画
class StaggeredAnimationPage extends StatefulWidget {
@override
StaggeredAnimationPageState createState() => StaggeredAnimationPageState();
}
class StaggeredAnimationPageState extends State<StaggeredAnimationPage>
with SingleTickerProviderStateMixin {
AnimationController controller;
Animation<Offset> translate;
Animation<double> sizeWidth;
Animation<double> sizeHeight;
Animation<double> radius;
Animation<Color> color;
Animation<double> opacity;
@override
void initState() {
super.initState();
controller =
AnimationController(vsync: this, duration: Duration(seconds: 2));
translate = Tween<Offset>(begin: Offset(0, 0), end: Offset(0, 200)).animate(
CurvedAnimation(
parent: controller,
curve: Interval(0, 0.3, curve: Curves.linear),
),
);
sizeWidth = Tween<double>(begin: 50, end: 200).animate(CurvedAnimation(
parent: controller,
curve: Interval(0.3, 0.4, curve: Curves.linear),
));
sizeHeight = Tween<double>(begin: 50, end: 200).animate(CurvedAnimation(
parent: controller,
curve: Interval(0.4, 0.6, curve: Curves.linear),
));
radius = Tween<double>(begin: 0, end: 100).animate(CurvedAnimation(
parent: controller,
curve: Interval(0.6, 0.7, curve: Curves.linear),
));
color = ColorTween(begin: Colors.green, end: Colors.red)
.animate(CurvedAnimation(
parent: controller,
curve: Interval(0.6, 0.8, curve: Curves.bounceOut),
));
opacity = Tween<double>(begin: 0, end: 1).animate(CurvedAnimation(
parent: controller,
curve: Interval(0.8, 1.0, curve: Curves.linear),
));
controller.addStatusListener((AnimationStatus status) {
if (status == AnimationStatus.completed) {
controller.reverse();
} else if (status == AnimationStatus.dismissed) {
controller.forward();
}
});
controller.forward();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('交错动画'),
),
body: Align(
alignment: Alignment.topCenter,
child: AnimatedBuilder(
animation: controller,
builder: (context, child) {
return Transform.translate(
offset: translate.value,
child: Container(
width: sizeWidth.value,
height: sizeHeight.value,
decoration: BoxDecoration(
border: Border.all(color: Colors.black, width: 1),
borderRadius: BorderRadius.all(Radius.circular(radius.value)),
color: color.value,
),
child: Opacity(
opacity: opacity.value,
child: Icon(
Icons.android,
size: 50,
color: Colors.white,
),
),
),
);
},
),
),
);
}
@override
void dispose() {
controller?.dispose();
super.dispose();
}
}