目录
- 基础动画概念和动画类
- 原始无动画部件
- 动画示例1(从小变大过渡)
- 动画示例2(使用 AnimatedWidget )
- 动画示例3(无限循环动画)
- 动画示例4(分离部件与动画)
- 动画示例5(非线性动画)
基础动画概念和动画类
Animation
Flutter动画库的核心类,任何动画都基于Animation
Animation
对象可以知道当前动画的状态(value
值),不过屏幕上显示的效果是一概不知的,即Animation
对象只负责动画过程中的数值变化AnimationController
管理动画,例如播放、反转,停用等CurvedAnimation
用于实现非线性动画,如快进慢出,慢进快出Tween
区间过渡插值,如数字从0到255,动画从黄到绿,分别可以用IntTween
、ColorTween
原始无动画部件
下面是一个没有动画,宽高固定为300的部件(Widget)
import 'package:flutter/material.dart';
void main() => runApp(LogoApp());
class LogoApp extends StatefulWidget {
_LogoAppState createState() => _LogoAppState();
}
class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
@override
Widget build(BuildContext context) {
return Center(
child: Container(
margin: EdgeInsets.symmetric(vertical: 10),
// 宽高固定
height: 300,
width: 300,
child: FlutterLogo(),
),
);
}
}
动画示例1(从小变大过渡)
实现图片从小到大(从宽高为0到宽高为300),步骤如下:
- 引入动画库
import 'package:flutter/animation.dart';
,并混入SingleTickerProviderStateMixin
- 创建动画控制类,此处可以定义动画的时长
- 利用
Tween
创建动画类,令值在0~300之间变化 - 利用
addListener
方法调用setState
更新value
import 'package:flutter/material.dart';
import 'package:flutter/animation.dart';
void main() => runApp(LogoApp());
class LogoApp extends StatefulWidget {
_LogoAppState createState() => _LogoAppState();
}
class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
// 定义动画变量
Animation<double> animation;
AnimationController controller;
@override
void initState() {
super.initState();
// 实例化控制类,时长3秒, vsync可以在当前动画对用户不可见时节省机器资源(不进行动画计算)
controller =
AnimationController(duration: const Duration(seconds: 2), vsync: this);
// 在0~300之间变动
animation = Tween<double>(begin: 0, end: 300).animate(controller)
..addListener(() {
// 通过setState令动画中的value产生变化
setState(() {});
});
// 开始播放动画
controller.forward();
}
@override
Widget build(BuildContext context) {
return Center(
child: Container(
margin: EdgeInsets.symmetric(vertical: 10),
// setState时value的变化产生在这里
height: animation.value,
width: animation.value,
child: FlutterLogo(),
),
);
}
@override
void dispose() {
// 注销动画实例,释放内存
controller.dispose();
super.dispose();
}
}
动画示例2(使用 AnimatedWidget )
使用AnimatedWidget
组件简化示例1的代码,不再需要手动调用setState
,步骤如下:
- 使用
AnimatedWidget
创建动画部件 - 在构造参数中接收
Animation
参数,并传递给父构造函数的listenable
参数 - 在
build
中通过listenable
获得动画中的value
import 'package:flutter/material.dart';
import 'package:flutter/animation.dart';
void main() => runApp(LogoApp());
class LogoApp extends StatefulWidget {
_LogoAppState createState() => _LogoAppState();
}
class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
Animation<double> animation;
AnimationController controller;
@override
void initState() {
super.initState();
controller =
AnimationController(duration: const Duration(seconds: 2), vsync: this);
// 不再需要手动setState
animation = Tween<double>(begin: 0, end: 300).animate(controller);
controller.forward();
}
@override
Widget build(BuildContext context) => AnimatedLogo(
animation: animation,
);
@override
void dispose() {
controller.dispose();
super.dispose();
}
}
class AnimatedLogo extends AnimatedWidget {
// 构造参数中的第二个参数为Animation,传递给父构造函数的listenable
AnimatedLogo({Key key, Animation<double> animation})
: super(key: key, listenable: animation);
Widget build(BuildContext context) {
// 通过listenable获取value
final Animation<double> animation = listenable;
return Center(
child: Container(
margin: EdgeInsets.symmetric(vertical: 10),
height: animation.value,
width: animation.value,
child: FlutterLogo(),
),
);
}
}
动画示例3(无限循环动画)
监控动画状态进度,并在过程中根据状态改变动画,实现动画的无限循环,步骤如下:
- 增加状态监听
addStatusListener
- 在监听到动画状态为
completed
(播放完毕)时,调用reverse()
方法反转动画,使动画回到初始状态 - 动画回到初始状态后停止时,状态为
dismissed
,此时再调用forward()
方法继续播放,实现无限循环
import 'package:flutter/material.dart';
import 'package:flutter/animation.dart';
void main() => runApp(LogoApp());
class LogoApp extends StatefulWidget {
_LogoAppState createState() => _LogoAppState();
}
class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
Animation<double> animation;
AnimationController controller;
@override
void initState() {
super.initState();
controller =
AnimationController(duration: const Duration(seconds: 2), vsync: this);
/***
* 通过addStatusListener获得当前动画进度,实现无限循环的动画
*
* AnimationStatus的四个状态
* 1. AnimationStatus.forward 动画播放
* 2. AnimationStatus.completed 动画播放完成
* 3. AnimationStatus.dismissed 动画回到初始状态后停止
* 4. AnimationStatus.reverse 动画反转
*/
animation = Tween<double>(begin: 0, end: 300).animate(controller)
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
// 动画完成后反转
controller.reverse();
} else if (status == AnimationStatus.dismissed) {
// 反转回初始状态时继续播放,实现无限循环
controller.forward();
}
});
// 播放动画
controller.forward();
}
@override
Widget build(BuildContext context) => AnimatedLogo(
animation: animation,
);
@override
void dispose() {
controller.dispose();
super.dispose();
}
}
class AnimatedLogo extends AnimatedWidget {
// 构造参数中的第二个参数为Animation,传递给父构造函数的listenable
AnimatedLogo({Key key, Animation<double> animation})
: super(key: key, listenable: animation);
Widget build(BuildContext context) {
// 通过listenable获取value
final Animation<double> animation = listenable;
return Center(
child: Container(
margin: EdgeInsets.symmetric(vertical: 10),
height: animation.value,
width: animation.value,
child: FlutterLogo(),
),
);
}
}
动画示例4(分离部件与动画)
在前面的动画中可以看到,部件显示的内容和动画混合在一起了,Flutter官网给出了一个可以使动画和部件内容分离的写法 按我理解,所有只是改变宽高(仅当前示例)的内容部件都可以使用这个GrowTransition动画过渡部件 步骤如下:
- 将部件内容创建为一个单独的内容部件
- 将过渡动画也创建为一个单独的部件,并使用
AnimatedBuilder
编写过渡的部分 - 将动画部件和内容部件组合在一起
import 'package:flutter/material.dart';
import 'package:flutter/animation.dart';
void main() => runApp(LogoApp());
class LogoApp extends StatefulWidget {
_LogoAppState createState() => _LogoAppState();
}
class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
Animation<double> animation;
AnimationController controller;
@override
void initState() {
super.initState();
// 创建动画
controller =
AnimationController(duration: const Duration(seconds: 2), vsync: this);
animation = Tween<double>(begin: 0, end: 300).animate(controller);
controller.forward();
}
// 将内容部件和动画组合起来
@override
Widget build(BuildContext context) => GrowTransition(
animation: animation,
child: LogoWidget(),
);
@override
void dispose() {
controller.dispose();
super.dispose();
}
}
// 内容部件
class LogoWidget extends StatelessWidget {
Widget build(BuildContext context) => Container(
margin: EdgeInsets.symmetric(vertical: 10),
child: FlutterLogo(),
);
}
// 动画过渡部件
class GrowTransition extends StatelessWidget {
GrowTransition({this.child, this.animation});
// 需要有动画过渡效果的部件
final Widget child;
// 为部件使用的动画
final Animation<double> animation;
Widget build(BuildContext context) => Center(
/**
* AnimatedBuilder将宽高定义为过渡状态
* 按我理解,所有只是改变宽高的内容部件都可以使用这个GrowTransition动画过渡部件
* 当然也可以定义一个只改变透明度的动画过渡部件复用
*/
child: AnimatedBuilder(
animation: animation,
builder: (context, child) => Container(
height: animation.value,
width: animation.value,
child: child,
),
child: child),
);
}
动画示例5(非线性动画)
非线性动画很好理解,就慢进快出,快进慢出等。使用CurvedAnimation
类,步骤如下:
- 创建
AnimationController
,定义相关参数 - 创建
CurvedAnimation
,是得其parent
参数为前一步创建的动画控制器,参数curve
为动画效果 - 创建
Tween
过渡值,调用evaluate()
方法,参数即为上一步创建的CurvedAnimation
对象 - 接着就调用
forward()
播放动画即可
import 'package:flutter/material.dart';
import 'package:flutter/animation.dart';
void main() => runApp(LogoApp());
class LogoApp extends StatefulWidget {
_LogoAppState createState() => _LogoAppState();
}
class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
Animation<double> animation;
AnimationController controller;
@override
void initState() {
super.initState();
controller =
AnimationController(duration: const Duration(seconds: 2), vsync: this);
// 曲线动画的核心的代码
animation = CurvedAnimation(parent: controller, curve: Curves.easeOut);
controller.forward();
}
@override
Widget build(BuildContext context) => AnimatedLogo(
animation: animation,
);
@override
void dispose() {
controller.dispose();
super.dispose();
}
}
class AnimatedLogo extends AnimatedWidget {
// 曲线动画使用的过渡效果
static final _sizeTween = Tween<double>(begin: 0, end: 300);
// 构造参数中的第二个参数为Animation,传递给父构造函数的listenable
AnimatedLogo({Key key, Animation<double> animation})
: super(key: key, listenable: animation);
Widget build(BuildContext context) {
// 通过listenable获取value
final Animation<double> animation = listenable;
return Center(
child: Container(
margin: EdgeInsets.symmetric(vertical: 10),
// 曲线动画部分开始
height: _sizeTween.evaluate(animation),
width: _sizeTween.evaluate(animation),
// 曲线动画部分结束
child: FlutterLogo(),
),
);
}
}
补充,另一个简单示例(修改动画示例1即可)
/**
* 非曲线动画的的另一个示例
* 把animate的参数从controller改成animate即可
*/
final Animation curve = CurvedAnimation(parent: controller, curve: Curves.easeOut);
animation = Tween<double>(begin: 0, end: 300).animate(curve);
controller.forward();