动画作为产品的重要组成部分,是提升用户体验的重要方式,一个恰当的动画不仅能够缓解用户因为等待而带来的情绪焦躁,还会增加应用的整体用户体验。因此,在应用中增加动画的相关功能,可以增强用户的粘性。

动画的原理

不管是Android平台还是iOS平台,我们在使用应用时都能看到一些炫酷的动画效果。作为移动应用的重要组成部分,动画是提高用户体验的重要手段,一个恰当的动画,不仅能够缓解用户因为等待而带来的情绪问题,还会提升用户使用的体验。 事实上,不管是什么视图框架,动画的实现原理都是相同的,即在一段有限的时间内,多次快速地改变视图外观来实现一个连续播放的效果。视图的一次改变即称为一个动画帧,对应一次屏幕刷新,而决定动画流畅度的一个重要指标就是帧率FPS(Frame Per Second缩写),即每秒的动画帧数。很明显,帧率越高则动画就会越流畅。 目前,大多数设备的屏幕刷新频率可以到达60Hz,而对于人眼来说,动画帧率超过16FPS就认为是流畅的,超过32FPS基本就感受不到任何卡顿。由于动画的每一帧都需要改变视图的输出,所以在一个时间段内连续的改变视图输出是比较耗费资源的,对设备的软硬件系统要求也比较高。作为衡量一个视图框架优劣的标准,Flutter框架在理想情况下是可以实现60FPS的,这和原生应用的帧率标准是基本是持平的。 同时,为了方便开发者创建并使用动画,不同的视图框架对动画都进行了高度的抽象和封装,比如在Android开发中,可以使用XML来描述一个动画然后再设置给一个视图对象。同样,Flutter也对动画进行了高度的抽象,并且提供了Animation、Curve、Controller、Tween等四个动画对象。 Animation是Flutter动画的核心抽象类,包含动画的当前值和状态两个属性。AnimationController是Animation的控制器,动画的开始、结束、停止、反向均由它控制,可以通过Listener和StatusListener来管理动画状态的改变。

动画API

在Flutter中,学习动画相关的开发,其实就是围绕Animation、Curve、Controller、Tween等四个动画对象来展开的。

Animation

在Flutter中,Animation是实现动画的核心类,Animation的主要作用就是保存动画的插值和状态,它本身与视图渲染没有任何关系。Animation对象则是一个可以在一段时间内依次生成一个区间值的类,其输出值可以是线性的、曲线的,可以是一个步进函数或者任何其他曲线函数等,由Curve来决定。Animation的核心源码如下:

abstract class Animation<T> extends Listenable implements ValueListenable<T> {const Animation();// 添加动画监听器@overridevoid addListener(VoidCallback listener);// 移除动画监听器@overridevoid removeListener(VoidCallback listener);// 添加动画状态监听器void addStatusListener(AnimationStatusListener listener);// 移除动画状态监听器void removeStatusListener(AnimationStatusListener listener);// 获取动画当前状态AnimationStatus get status;// 获取动画当前的值@overrideT get value;

Animation是一个抽象类,Widget可以直接将这些动画合并到自己的build方法中来读取它们的当前值或者监听它们的状态变化。Animation提供了addListener和addStatusListener两个方法来监听动画帧的变化。

addListener addListener方法用于给Animation对象添加帧监听器,每一帧都会被调用,当帧监听器监听到状态发生改变后会调用setState()来触发视图的重建。这意味着: - 每当动画的状态值发生变化时,动画都会通知所有通过 addListener 添加的监听器。 - 一个正在监听动画的state对象会调用自身的setState方法,将自身传入这些监听器的回调函数来通知 widget 系统需要根据新状态值进行重新构建。

addStatusListener addStatusListener方法用于给Animation对象添加动画状态改变监听器,动画开始、结束、正向或反向时会调用状态改变的监听器。这意味着:

  • 当动画的状态发生变化时,会通知所有通过 addStatusListener 添加的监听器。
  • 动画会从 dismissed 状态开始,表示它处于变化区间的开始点。
  • 举例来说,从 0.0 到 1.0 的动画在 dismissed 状态时的值应该是 0.0。
  • 动画进行的下一状态可能是 forward(比如从 0.0 到 1.0)或者 reverse(比如从 1.0 到 0.0)。
  • 最终,如果动画到达其区间的结束点(比如 1.0),则动画会变成 completed 状态

AnimationController

AnimationController,即动画控制器,Animation是一个抽象类,并不能用来直接创建对象并实现动画,它的主要用于控制动画的开始、结束、停止、反向等操作。AnimationController是Animation的一个子类,默认情况下,AnimationController会在给定的时间段内以线性的方式生成从0.0到1.0的数字。它的源码如下:

class AnimationController extends Animation<double>with AnimationEagerListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {AnimationController({// 初始化值double value,// 动画执行的时间this.duration,// 反向动画执行的时间this.reverseDuration,// 最小值this.lowerBound = 0.0,// 最大值this.upperBound = 1.0,// 刷新率ticker的回调(看下面详细解析)@required TickerProvider vsync,})
}

其中,AnimationController有一个必传的参数vsync,那么AnimationController有什么作用呢?之前我讲过关于Flutter的渲染闭环,Flutter每次渲染一帧画面之前都需要等待一个vsync信号。这里也是为了监听vsync信号,当Flutter开发的应用程序不再接受同步信号时(比如锁屏或退到后台),那么继续执行动画会消耗性能,开发中比较常见的解决方法是将SingleTickerProviderStateMixin混入到State的定义中。例如,下面是一个比较简单的数字自动增加动画的示例。

import 'package:flutter/material.dart';
import 'package:gc_data_app/utils/utils.dart';class AnimText extends StatefulWidget {final int number;final int duration;final Color fontColor;final double fontSize;const AnimText({Key key,this.number,this.duration,this.fontColor,this.fontSize,}) : super(key: key);@overrideState<StatefulWidget> createState() {return AnimState();}
}class AnimState extends State<AnimText> with SingleTickerProviderStateMixin {AnimationController controller;Animation animation;var begin=0;@overridevoid initState() {super.initState();controller = AnimationController(vsync: this, duration: Duration(milliseconds: widget.duration));final Animation curve=CurvedAnimation(parent: controller,curve: Curves.linear);animation = IntTween(begin: begin, end: widget.number).animate(curve)..addStatusListener((status) {if(status==AnimationStatus.completed){
//         controller.reverse();}});}@overrideWidget build(BuildContext context) {controller.forward();return AnimatedBuilder(animation: controller,builder: (context,child){return Container(child:Text(Utils.formatMoney(animation.value),style: TextStyle(fontSize: widget.fontSize, color: widget.fontColor,fontWeight: FontWeight.bold)),);} ,);}@overridevoid dispose() {controller.dispose();super.dispose();}
}

CurvedAnimation

CurvedAnimation是Animation的一个实现类,它的目的是为了给AnimationController增加动画曲线。通常,动画过程可以是匀速的、匀加速的或者先加速后减速等。Flutter通过Curve来描述动画过程,我们可以把匀速动画称为线性动画,把非匀速动画称为非线性动画。

CurvedAnimation可以将AnimationController和Curve结合起来,生成一个新的Animation对象。例如:

class CurvedAnimation extends Animation<double> with AnimationWithParentMixin<double> {CurvedAnimation({// 通常传入一个AnimationController@required this.parent,// Curve类型的对象@required this.curve,this.reverseCurve,});
}

Curve类型的对象的有一些常量Curves可以直接使用,常用的有如下一些: - linear:匀速动画 - decelerate:匀减速动画 - ease:先加速后减速 - easeIn:先快后慢动画 - easeOut:先慢后快动画 - easeInOut:先慢,然后加速,最后减速

Tween

默认情况下,AnimationController对象的取值范围是[0.0,1.0],如果需要给动画设置不同的范围或者类型的值时,可以使用Tween来定义并生成不同范围或类型的值。Tween的源码非常简单,传入两个值即可,如下所示。

class Tween<T extends dynamic> extends Animatable<T> {Tween({ this.begin, this.end });
}

Tween继承自Animatable,而不是继承自Animation,Animatable是一个控制动画类型的类,主要定义了动画值的映射规则。虽然,Animatable和Animation有很多相似之处,但它的类型可以是除double的其他类型。例如,下面是使用ColorTween实现颜色渐变的过渡动画的例子。

Tween colorTween =new ColorTween(begin: Colors.transparent, end: Colors.black54);

Tween也有一些子类,比如ColorTween、BorderTween,可以针对动画或者边框来设置动画的值。

动画示例

和原生平台的动画开发一样,Flutter的动画开发也有一定的规则,实际使用时,只需要按照遵循步骤即可。通用的步骤如下: 1.创建AnimationController和Animation; 2.设置动画的类型,监听动画执行 3.销毁动画

例如,下面是Flutter动画的基本使用示例,代码如下:

import 'package:demos/page/anim_page.dart';
import 'package:flutter/material.dart';void main() {runApp(MyApp());
}class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return MaterialApp(title: 'Flutter Demo',theme: ThemeData(primarySwatch: Colors.blue,visualDensity: VisualDensity.adaptivePlatformDensity,),home: MyHomePage(),);}
}class MyHomePage extends StatelessWidget {@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('Flutter 动画'),),body: HeartAnimationWidget(key: animKey),floatingActionButton: FloatingActionButton(child: Icon(Icons.add),onPressed: () {if (!animKey.currentState.controller.isAnimating) {animKey.currentState.controller.forward();} else {animKey.currentState.controller.stop();}},),);}
}GlobalKey<_HeartAnimationPageState> animKey = GlobalKey();class HeartAnimationPage extends StatefulWidget {HeartAnimationPage({Key key}): super(key: key);@override_HeartAnimationPageState createState() => _HeartAnimationPageState();
}class _HeartAnimationPageState extends State<HeartAnimationPage> with SingleTickerProviderStateMixin {AnimationController controller;Animation<double> animation;@overridevoid initState() {super.initState();// 1.创建AnimationControllercontroller = AnimationController(duration: Duration(seconds: 1), vsync: this);// 2.动画添加Curve效果animation = CurvedAnimation(parent: controller, curve: Curves.elasticInOut, reverseCurve: Curves.easeOut);// 3.监听动画animation.addListener(() {setState(() {});});// 4.控制动画的翻转animation.addStatusListener((status) {if (status == AnimationStatus.completed) {controller.reverse();} else if (status == AnimationStatus.dismissed) {controller.forward();}});// 5.设置值的范围animation = Tween(begin: 50.0, end: 120.0).animate(controller);}@overrideWidget build(BuildContext context) {return Center(child: Icon(Icons.favorite, color: Colors.red, size: animation.value,),);}@overridevoid dispose() {controller.dispose();super.dispose();}
}

运行上面的代码,当点击案例后执行一个心跳动画,可以反复执行,再次点击可以暂停和重新开始动画,运行效果如下图所示。

AnimatedWidget

通过addListener()和setState()来更新视图是动画实现的通用的做法,但缺点是需要在每个动画中都添加监听函数,代码比较冗余,并且调用setState()方法意味着整个State类中的build方法就会被重新构建,性能损耗比较严重。因此,官方推荐使用AnimatedWidget类来实现同样的动画效果,因为AnimatedWidget类简化了addListener()和setState()的调用流程,并隐藏了底层额实现细节。

对于上面的示例,我们西安创建一个继承自AnimatedWidget的Widget,如下所示。

class HeatAnimationWidget extends AnimatedWidget {HeatAnimationWidget(Animation animation): super(listenable: animation);@overrideWidget build(BuildContext context) {Animation animation = listenable;return Icon(Icons.favorite, color: Colors.red, size: animation.value,);}
}

然后,我们对HeartAnimationPage的代码进行如下修改。

class HeartAnimationPage extends StatefulWidget {HeartAnimationPage({Key key}): super(key: key);@override_HeartAnimationPageState createState() => _HeartAnimationPageState();
}class _HeartAnimationPageState extends State<HeartAnimationPage> with SingleTickerProviderStateMixin {AnimationController controller;Animation<double> animation;@overridevoid initState() {super.initState();// 1.创建AnimationControllercontroller = AnimationController(duration: Duration(seconds: 1), vsync: this);// 2.动画添加Curve效果animation = CurvedAnimation(parent: controller, curve: Curves.elasticInOut, reverseCurve: Curves.easeOut);// 3.监听动画// 4.控制动画的翻转animation.addStatusListener((status) {if (status == AnimationStatus.completed) {controller.reverse();} else if (status == AnimationStatus.dismissed) {controller.forward();}});// 5.设置值的范围animation = Tween(begin: 50.0, end: 120.0).animate(controller);}@overrideWidget build(BuildContext context) {return Center(child: HeatAnimationWidget(animation),);}@overridevoid dispose() {controller.dispose();super.dispose();}
}

AnimatedBuilder

通过AnimatedWidget类,我们可以从动画中分离出组件,从而将动画和组件分离开来,不过动画的渲染过程仍然在AnimatedWidget中执行。如果想要将动画的渲染过程分离出来,可以使用AnimatedBuilder类,与AnimatedWidget的作用类似,AnimatedBuilder可以自动监听Animation的变化,然后根据需要自动刷新视图。

因此,在上面的示例中,我们可以使用AnimatedBuilder进行如下的优化:

class HeartAnimationPage extends StatefulWidget {HeartAnimationPage({Key key}): super(key: key);@override_HeartAnimationPageState createState() => _HeartAnimationPageState();
}class _HeartAnimationPageState extends State<HeartAnimationPage> with SingleTickerProviderStateMixin {AnimationController controller;Animation<double> animation;@overridevoid initState() {super.initState();// 1.创建AnimationControllercontroller = AnimationController(duration: Duration(seconds: 1), vsync: this);// 2.动画添加Curve效果animation = CurvedAnimation(parent: controller, curve: Curves.elasticInOut, reverseCurve: Curves.easeOut);// 3.监听动画// 4.控制动画的翻转animation.addStatusListener((status) {if (status == AnimationStatus.completed) {controller.reverse();} else if (status == AnimationStatus.dismissed) {controller.forward();}});// 5.设置值的范围animation = Tween(begin: 50.0, end: 120.0).animate(controller);}@overrideWidget build(BuildContext context) {return Center(child: AnimatedBuilder(animation: animation,builder: (ctx, child) {return Icon(Icons.favorite, color: Colors.red, size: animation.value,);},));}@overridevoid dispose() {controller.dispose();super.dispose();}
}

除了上面介绍的动画外,Flutter还提供了交错动画和Hero动画。

flutter 移动通知_Flutter开发之动画相关推荐

  1. flutter 移动通知_Flutter移动电商实战 --(24)Provide状态管理基础

    点击左侧的导航右侧的导航发生变化,这属于两个widget.这两个widget之间进行通讯.用setstate的形式,耦合性太强 状态管理有这么几种方案 这里我们用Provide,谷歌自己推出的 开始写 ...

  2. flutter图标按钮_Flutter开发第一个项目android studio 开发工具的使用说明

    Flutter开发第一个项目android studio 开发工具的使用说明 做个自我介绍 自我介绍还是有必要介绍一下的,毕竟这是网络里,你看不到我,我看不到你,只能通过文字来传递信息,本人做技术8年 ...

  3. Flutter框架分析(五)-- 动画

    Flutter框架分析分析系列文章: <Flutter框架分析(一)-- 总览和Window> <Flutter框架分析(二)-- 初始化> <Flutter框架分析(三 ...

  4. 53、Flutter之通知 Notification

    通知(Notification)是Flutter中一个重要的机制,在widget树中,每一个节点都可以分发通知,通知会沿着当前节点向上传递,所有父节点都可以通过NotificationListener ...

  5. 【Flutter进阶】 Web开发中如何加载网络图片

    目录 前言 如何加载网络图片 列表中的网络图片 HTML renderer和CanvasKit 文本无法选择 Shadow Root问题 为什么找不到节点? Shadow Dom 问题解决 Flutt ...

  6. android调用flutter aar_Flutter原生混合开发

    混合开发简介 使用Flutter从零开始开发App是一件轻松惬意的事情,但对于一些成熟的产品来说,完全摒弃原有App的历史沉淀,全面转向Flutter是不现实的.因此使用Flutter去统一Andro ...

  7. unity重定向_unity3D游戏开发之动画混合与动画重定向

    Unity3D 游戏开发之动画混合与动画重定向 动画混合 状态机之中的状态不仅可以是单个剪辑, 也可以是一个混合树. 构建和编辑 复杂的状态机和混合树,以便完全控制的角色如何运动. Unity 编辑器 ...

  8. Flutter 通知栏通知

    本章节叙述Flutter 通知栏通知功能,主要是使用 Flutter与原生交互功能调用Android发送通知. 效果图 所需知识 Flutter构建通道机制Channel Android创建通知渠道N ...

  9. uni-app实战之社区交友APP(10)登录、个人空间开发和动画优化

    文章目录 前言 一.登录页开发 1.pages.json配置 2.登录页UI构建 3.登录类型切换实现 4.登录表单验证功能实现 5.第三方登录组件功能实现 二.个人空间开发 1.pages.json ...

最新文章

  1. 蓝桥杯 最长公共子序列
  2. Android 缓存机制
  3. ubuntu19.10安装codeblocks20.03
  4. Yum本地Rpm库设置
  5. Theano 中文文档 0.9 - 5.1 Ubuntu安装说明
  6. dl360 g7安装linux,HPDL360G7服务器安装说明.ppt
  7. CentOS 7安装MongoDB
  8. ubuntu中mysql安装失败
  9. 【毕设教学】 经典单片机控制算法:PID - 嵌入式 物联网
  10. 工业相机和镜头主要参数解释
  11. 计算机专用的英语词汇总结
  12. html中siblings方法,jQuery siblings()方法的用法
  13. ali-oss配合element上传130M以上的文件,浏览器直接崩溃,有大神知道是什么情况么
  14. Android 手机遥控器添加模拟鼠标功能
  15. 教你在线网页批量免费拆分PDF
  16. 计算机Excel怎么弄迷你图,【迷你图excel在哪里】Excel如何绘制迷你图
  17. iOS——6种系统手势操作
  18. cas mysql_使用CAS连接Mysql数据库
  19. html足球球面插件,三维效果的黄金足球球面揭示开场片头AE模板
  20. P - Consumer

热门文章

  1. 程序员的算法课(15)-分治法获取文件中出现频次最高100词
  2. Python实战——2048
  3. android设置wifiip地址,android Wifi 设置静态ip地址的方法
  4. java web 统计_Java web网站访问量的统计
  5. elasticsarch6.5.4安装插件 searchguard和elasticsearh-head插件安全性问题
  6. 时间格式化及操作(moment.js篇)
  7. SSE图像算法优化系列十八:三次卷积插值的进一步SSE优化。
  8. linux下清空c++ cin无效流的方式
  9. 从Word,Excel中提取Flash
  10. 加速你的Hibernate引擎(下)