【Flutter实战】动画核心(2/2)
老孟导读:这是Flutter动画系统核心的第二部分,接上一篇。这一篇主要讲解动画曲线、自定义动画曲线,以及AnimationController 、Tween 、Curve 三者之间的关系。
Curve
动画中还有一个重要的概念就是 Curve,即动画执行曲线。Curve 的作用和 Android 中的 Interpolator(差值器)是一样的,负责控制动画变化的速率,通俗地讲就是使动画的效果能够以匀速、加速、减速、抛物线等各种速率变化。
蓝色盒子大小 100 变大到 200,动画曲线设置为 bounceIn(弹簧效果) :
class CurveDemo extends StatefulWidget {
@override
_CurveDemoState createState() => _CurveDemoState();
}
class _CurveDemoState extends State<CurveDemo>
with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation _animation;
@override
void initState() {
super.initState();
_controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 1000))
..addListener(() {
setState(() {});
});
_animation = Tween(begin: 100.0, end: 200.0)
.chain(CurveTween(curve: Curves.bounceIn))
.animate(_controller);
}
@override
Widget build(BuildContext context) {
return Center(
child: GestureDetector(
onTap: () {
_controller.forward();
},
child: Container(
height: _animation.value,
width: _animation.value,
color: Colors.blue,
alignment: Alignment.center,
child: Text(
'点我变大',
style: TextStyle(color: Colors.white, fontSize: 18),
),
),
),
);
}
@override
void dispose() {
super.dispose();
_controller.dispose();
}
}
动画加上Curve 后,AnimationController 的最小/大值必须是 [0,1]之间,例如下面的写法就是错误的:
_controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 1000),lowerBound: 100.0,upperBound: 200.0)
..addListener(() {
setState(() {});
});
_animation = CurveTween(curve: Curves.bounceIn).animate(_controller);
抛出如下异常:
正确写法:
_controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 1000))
..addListener(() {
setState(() {});
});
_animation = Tween(begin: 100.0, end: 200.0)
.chain(CurveTween(curve: Curves.bounceIn))
.animate(_controller);
系统已经提供了38种常用到动画曲线:
linear
decelerate
bounceIn**
其余动画效果可到 Curve 源代码中查看。
通常情况下,这些曲线能够满足 99.99% 的需求,很多时候设计也就是告诉你动画 先快后慢 或者 先慢后快,所以选个类似的就可以了,但有一些 特别 的设计非要一个系统没有的动画曲线,要怎么办?
那就自定义一个动画曲线
其实自定义一个动画曲线难点在数学上,怎么把数学公式用代码实现才是难点。
下面是一个 楼梯效果 的动画曲线:
自定义动画曲线需要继承 Curve 重写 transformInternal 方法即可:
class _StairsCurve extends Curve {
@override
double transformInternal(double t) {
return t;
}
}
直接返回 t 其实就是线性动画,即 Curves.linear,实现楼梯效果动画代码如下:
class _StairsCurve extends Curve {
//阶梯的数量
final int num;
double _perStairY;
double _perStairX;
_StairsCurve(this.num) {
_perStairY = 1.0 / (num - 1);
_perStairX = 1.0 / num;
}
@override
double transformInternal(double t) {
return _perStairY * (t / _perStairX).floor();
}
}
修改开始处的案例,使用此曲线:
_animation = Tween(begin: 100.0, end: 200.0)
.chain(CurveTween(curve: _StairsCurve(5)))
.animate(_controller);
总结
动画系统的核心是 AnimationController,而且是不可或缺的,动画中必须有 AnimationController,而 Tween 和 Curve 则是对 AnimationController 的补充, Tween 实现了将 AnimationController [0,1]的值映射为其他类型的值,比如颜色、样式等,Curve 是 AnimationController 动画执行曲线,默认是线性运行。
将 AnimationController 、 Tween 、Curve 进行关联的方式:
AnimationController _controller;
Animation _animation;
@override
void initState() {
super.initState();
_controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 1000))
..addListener(() {
setState(() {});
});
_animation = Tween(begin: 100.0, end: 200.0)
.animate(_controller);
}
或者:
_animation = _controller.drive(Tween(begin: 100.0, end: 200.0));
加入 Curve :
_animation = Tween(begin: 100.0, end: 200.0)
.chain(CurveTween(curve: Curves.linear))
.animate(_controller);
或者:
_animation = _controller
.drive(CurveTween(curve: Curves.linear))
.drive(Tween(begin: 100.0, end: 200.0));
只需要 Curve :
_animation = CurveTween(curve: Curves.linear)
.animate(_controller);
或者
_animation = _controller.drive(CurveTween(curve: Curves.linear));
一个 AnimationController 可以对应多个 Animation(Tween 或者 Curve),StatefulWidget 组件可以包含多个 AnimationController ,但 SingleTickerProviderStateMixin 需要修改为 TickerProviderStateMixin,改变颜色和大小,由两个 AnimationController 控制:
class MultiControllerDemo extends StatefulWidget {
@override
_MultiControllerDemoState createState() => _MultiControllerDemoState();
}
class _MultiControllerDemoState extends State<MultiControllerDemo>
with TickerProviderStateMixin {
AnimationController _sizeController;
AnimationController _colorController;
Animation<double> _sizeAnimation;
Animation<Color> _colorAnimation;
@override
void initState() {
super.initState();
_sizeController =
AnimationController(vsync: this, duration: Duration(milliseconds: 2000))
..addListener(() {
setState(() {});
});
_sizeAnimation = _sizeController
.drive(CurveTween(curve: Curves.linear))
.drive(Tween(begin: 100.0, end: 200.0));
_colorController =
AnimationController(vsync: this, duration: Duration(milliseconds: 1000))
..addListener(() {
setState(() {});
});
_colorAnimation = _colorController
.drive(CurveTween(curve: Curves.bounceIn))
.drive(ColorTween(begin: Colors.blue, end: Colors.red));
}
@override
Widget build(BuildContext context) {
return Center(
child: GestureDetector(
onTap: () {
_sizeController.forward();
_colorController.forward();
},
child: Container(
height: _sizeAnimation.value,
width: _sizeAnimation.value,
color: _colorAnimation.value,
alignment: Alignment.center,
child: Text(
'点我变化',
style: TextStyle(color: Colors.white, fontSize: 18),
),
),
),
);
}
@override
void dispose() {
super.dispose();
_sizeController.dispose();
_colorController.dispose();
}
}
AnimationController 、Tween 、Curve 是整个动画的基础,Flutter 系统封装了大量了动画组件,但这些组件也是基于此封装的,因此深入了解这三部分比学习使用动画组件更重要,再次对这3个进行总结:
- AnimationController:动画控制器,控制动画的播放、停止等。继承自Animation< double >,是一个特殊的Animation对象,默认情况下它会线性的生成一个0.0到1.0的值,类型只能是 double 类型,不设置动画曲线的情况下,可以设置输出的最小值和最大值。
- Curve:动画曲线,作用和Android中的Interpolator(差值器)类似,负责控制动画变化的速率,通俗地讲就是使动画的效果能够以匀速、加速、减速、抛物线等各种速率变化。
- Tween:将 AnimationController 生成的 [0,1]值映射成其他属性的值,比如颜色、样式等。
完成一个动画效果的过程如下:
- 创建 AnimationController 。
- 如果需要 Tween 或者 Curve,将 AnimationController 与其关联,Tween 和 Curve 并不是必须的,当然大部分情况都需要。
- 将动画值作用于组件,当没有Tween 和 Curve 时,动画值来源于AnimationController,如果有 Tween 和 Curve,动画值来源于 Tween 或者Curve 的 Animation。
如果你发现阅读完此篇文章还是感觉不会写动画,不要灰心,这是正常的,第一次想了解这些抽象的概念还是比较困难的,如果你有其他平台的相关经验,那会好很多,对于动画,想要掌握个人认为只有一个方法就是 多写。
后面会介绍动画组件基础使用、实现原理、高级动画以及自定义动画,把每一个动画组件的用法都亲自手写一遍(而不是复制黏贴),回过头来在看这篇文章,会有不一样的感觉。
- Spring实战——无需一行xml配置实现自动化注入
- 基于改进人工蜂群算法的K均值聚类算法(附MATLAB版源代码)
- RabbitMQ入门-Routing直连模式
- WCF技术剖析之三十二:一步步创建一个完整的分布式事务应用
- .NET的资源并不限于.resx文件,你可以采用任意存储形式[上篇]
- RabbitMQ入门-消息订阅模式
- 年终盘点:2018最值得学习的几种热门编程语言
- 如何编写没有Try/Catch的程序
- RabbitMQ入门-消息派发那些事儿
- RabbitMQ入门-高效的Work模式
- 谈谈分布式事务之四: 两种事务处理协议OleTx与WS-AT
- RabbitMQ入门-从HelloWorld开始
- RabbitMQ入门-从HelloWorld开始
- RabbitMQ入门-初识RabbitMQ
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 单细胞转录组高级分析四:scRNA数据推断CNV
- 0808-7.1.1-如何在CDP7.1.1指定Hive SQL的资源池队列
- iOS多线程之GCD、OperationQueue 对比和实践记录
- singleR的7个数据库文件下载失败的解决方案
- Spring Boot 如何快速集成 Redis 哨兵?
- 一行命令实现成“吨”测试数据的转码
- Jenkins参数化构建与触发
- 数据无法模拟,自动化受阻怎么办?
- Quickprop介绍:一个加速梯度下降的学习方法
- PandaSQL:一个让你能够通过SQL语句进行pandas的操作的python包
- 每个数据科学家都应该知道的20个NumPy操作
- 机器学习特性缩放的介绍,什么时候为什么使用
- 聊聊claudb的set command
- 聊聊claudb的zset command
- 聊聊claudb的pubsub command