动画Animation开发指南

  • 在Flutter中有哪些类型的动画?
  • 如何使用动画库中的基础类给widget添加动画?
  • 如何为动画添加监听器?
  • 该什么时候使用AnimatedWidget与AnimatedBuilder?
  • 如何使用Hero动画?

精心设计的动画会让用户界面感觉更直观、流畅,能改善用户体验。 Flutter的动画支持可以轻松实现各种动画类型。许多widget,特别是Material Design widgets, 都带有在其设计规范中定义的标准动画效果,但也可以自定义这些效果。

在Flutter中有哪些类型的动画?

在Flutter中动画分为两类:基于tween或基于物理的。

推荐大家查阅我们上面课程中所讲到的Flutter gallery中的示例代码来学习动画。

  • 补间(Tween)动画:在补间动画中,定义了开始点和结束点、时间线以及定义转换时间和速度的曲线。然后由框架计算如何从开始点过渡到结束点。
  • 基于物理的动画:在基于物理的动画中,运动被模拟为与真实世界的行为相似。例如,当你掷球时,它在何处落地,取决于抛球速度有多快、球有多重、距离地面有多远。 类似地,将连接在弹簧上的球落下(并弹起)与连接到绳子上的球放下的方式也是不同。

如何使用动画库中的基础类给widget添加动画?

在为widget添加动画之前,先让我们认识下动画的几个朋友:

Animation

在Flutter中,Animation对象本身和UI渲染没有任何关系。Animation是一个抽象类,它拥有其当前值和状态(完成或停止)。其中一个比较常用的Animation类是Animation<double>

Flutter中的Animation对象是一个在一段时间内依次生成一个区间之间值的类。Animation对象的输出可以是线性的、曲线的、一个步进函数或者任何其他可以设计的映射。 根据Animation对象的控制方式,动画可以反向运行,甚至可以在中间切换方向。

CurvedAnimation

CurvedAnimation将动画过程定义为一个非线性曲线。

final CurvedAnimation curve =new CurvedAnimation(parent: controller, curve: Curves.easeIn);

注: Curves 类定义了许多常用的曲线,也可以创建自己的,例如:

class ShakeCurve extends Curve {@overridedouble transform(double t) {return math.sin(t * math.PI * 2);}
}

AnimationController

AnimationController是一个特殊的Animation对象,在屏幕刷新的每一帧,就会生成一个新的值。默认情况下,AnimationController在给定的时间段内会线性的生成从0.0到1.0的数字。 例如,下面代码创建一个Animation对象:

final AnimationController controller = new AnimationController(duration: const Duration(milliseconds: 2000), vsync: this);

AnimationController派生自Animation<double>,因此可以在需要Animation对象的任何地方使用。 但是,AnimationController具有控制动画的其他方法:

当创建一个AnimationController时,需要传递一个vsync参数,存在vsync时会防止屏幕外动画消耗不必要的资源,可以将stateful对象作为vsync的值。

注意: 在某些情况下,值(position,值动画的当前值)可能会超出AnimationController的0.0-1.0的范围。例如,fling()函数允许您提供速度(velocity)、力量(force)、position(通过Force对象)。位置(position)可以是任何东西,因此可以在0.0到1.0范围之外。 CurvedAnimation生成的值也可以超出0.0到1.0的范围。根据选择的曲线,CurvedAnimation的输出可以具有比输入更大的范围。例如,Curves.elasticIn等弹性曲线会生成大于或小于默认范围的值。

Tween

默认情况下,AnimationController对象的范围从0.0到1.0。如果您需要不同的范围或不同的数据类型,则可以使用Tween来配置动画以生成不同的范围或数据类型的值。例如,以下示例,Tween生成从-200.0到0.0的值:

final Tween doubleTween = new Tween<double>(begin: -200.0, end: 0.0);

Tween是一个无状态(stateless)对象,需要begin和end值。Tween的唯一职责就是定义从输入范围到输出范围的映射。输入范围通常为0.0到1.0,但这不是必须的。

Tween继承自Animatable<T>,而不是继承自Animation<T>。Animatable与Animation相似,不是必须输出double值。例如,ColorTween指定两种颜色之间的过渡。

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

Tween对象不存储任何状态。相反,它提供了evaluate(Animation<double> animation)方法将映射函数应用于动画当前值。 Animation对象的当前值可以通过value()方法取到。evaluate函数还执行一些其它处理,例如分别确保在动画值为0.0和1.0时返回开始和结束状态。

Tween.animate

要使用Tween对象,可调用它的animate()方法,传入一个控制器对象。例如,以下代码在500毫秒内生成从0到255的整数值。

final AnimationController controller = new AnimationController(duration: const Duration(milliseconds: 500), vsync: this);
Animation<int> alpha = new IntTween(begin: 0, end: 255).animate(controller);

注意animate()返回的是一个Animation,而不是一个Animatable。

以下示例构建了一个控制器、一条曲线和一个Tween:

final AnimationController controller = new AnimationController(duration: const Duration(milliseconds: 500), vsync: this);
final Animation curve =new CurvedAnimation(parent: controller, curve: Curves.easeOut);
Animation<int> alpha = new IntTween(begin: 0, end: 255).animate(curve);

为widget添加动画

在下面的实例中我们为一个logo添加了一个从小放大的动画:

import 'package:flutter/animation.dart';
import 'package:flutter/material.dart';void main() => runApp(LogoApp());class LogoApp extends StatefulWidget {_LogoAppState createState() => _LogoAppState();
}class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {late Animation<double> animation;late AnimationController controller;AnimationStatus? animationState;double? animationValue;@overridevoid initState() {super.initState();controller =AnimationController(duration: const Duration(seconds: 2), vsync: this);// #docregion addListeneranimation = Tween<double>(begin: 0, end: 300).animate(controller)..addListener(() {// #enddocregion addListenersetState(() {animationValue = animation.value;});// #docregion addListener})..addStatusListener((AnimationStatus state) {setState(() {animationState = state;});});// #enddocregion addListener}@overrideWidget build(BuildContext context) {return Container(margin: EdgeInsets.only(top: 50),child: Column(children: <Widget>[GestureDetector(onTap: () {controller.reset();controller.forward();},child: Text('Start', textDirection: TextDirection.ltr),),Text('State:' + animationState.toString(),textDirection: TextDirection.ltr),Text('Value:' + animationValue.toString(),textDirection: TextDirection.ltr),Container(height: animation.value,width: animation.value,child: FlutterLogo(),),],),);}@overridevoid dispose() {controller.dispose();super.dispose();}
}

注意,在上述代码中要实现这个动画的关键一步是在addListener()的回调中添加setState的调用这样才能触发页面重新渲染,动画才能有效,另外也可以通过AnimatedWidget来实现,在下文中会讲到。

如何为动画添加监听器?

有时我们需要知道动画执行的进度和状态,在Flutter中我们可以通过Animation的addListeneraddStatusListener方法为动画添加监听器:

 @overridevoid initState() {super.initState();controller =AnimationController(duration: const Duration(seconds: 2), vsync: this);animation = Tween<double>(begin: 0, end: 300).animate(controller)// #enddocregion print-state..addStatusListener((status) {if (status == AnimationStatus.completed) {controller.reverse();} else if (status == AnimationStatus.dismissed) {controller.forward();}})// #docregion print-state..addStatusListener((state) => print('$state'));..addListener(() {// #enddocregion addListenersetState(() {// The state that has changed here is the animation object’s value.});// #docregion addListener});controller.forward();}

可对照学习为widget添加动画的例子;

用AnimatedWidget与AnimatedBuilder简化和重构我们对动画的使用

什么是AnimatedWidget?

我们可以将AnimatedWidget理解为Animation的助手,使用它可以简化我们对动画的使用,在为widget添加动画的学习中我们不难发现,在不使用AnimatedWidget的情况下需要手动调用动画的addListener()并在回调中添加setState才能看到动画效果,AnimatedWidget将为我们简化这一操作。

在下面的重构示例中,LogoApp现在继承自AnimatedWidget而不是StatefulWidgetAnimatedWidget在绘制时使用动画的当前值。LogoApp仍然管理着AnimationControllerTween

import 'package:flutter/animation.dart';
import 'package:flutter/material.dart';class AnimatedLogo extends AnimatedWidget {const AnimatedLogo({Key? key, required Animation<double> animation}): super(key: key, listenable: animation);@overrideWidget build(BuildContext context) {final Animation<double> animation = listenable as Animation<double>;return Center(child: Container(margin: const EdgeInsets.symmetric(vertical: 10.0),height: animation.value,width: animation.value,child: const FlutterLogo(),),);}
}class LogoApp extends StatefulWidget {const LogoApp({Key? key}) : super(key: key);@override_LogoAppState createState() => _LogoAppState();
}class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {late AnimationController controller;late  Animation<double> animation;@overrideinitState() {super.initState();controller = AnimationController(duration: const Duration(milliseconds: 2000), vsync: this);animation = Tween(begin: 0.0, end: 300.0).animate(controller);controller.forward();}@overrideWidget build(BuildContext context) {return AnimatedLogo(animation: animation);}@overridedispose() {controller.dispose();super.dispose();}
}void main() {runApp(const LogoApp());
}

什么是AnimatedBuilder?

AnimatedBuilder是用于构建动画的通用widget,AnimatedBuilder对于希望将动画作为更大构建函数的一部分包含在内的更复杂的widget时非常有用,其实你可以这样理解:AnimatedBuilder是拆分动画的一个工具类,借助它我们可以将动画和widget进行分离:

在上面的实例中我们的代码存在的一个问题: 更改动画需要更改显示logo的widget。更好的解决方案是将职责分离:

接下来我们就借助AnimatedBuilder类来完成此分离。AnimatedBuilder是渲染树中的一个独立的类, 与AnimatedWidget类似,AnimatedBuilder自动监听来自Animation对象的通知,不需要手动调用addListener()

我们根据下图的 widget 树来创建我们的代码:

import 'package:flutter/animation.dart';
import 'package:flutter/material.dart';void main() => runApp(LogoApp());// #docregion LogoWidget
class LogoWidget extends StatelessWidget {const LogoWidget({Key? key}) : super(key: key);// Leave out the height and width so it fills the animating parent@overrideWidget build(BuildContext context) => Container(margin: const EdgeInsets.symmetric(vertical: 10),child: const FlutterLogo(),);
}
// #enddocregion LogoWidget// #docregion GrowTransition
class GrowTransition extends StatelessWidget {const GrowTransition({required this.child, required this.animation});final Widget child;final Animation<double> animation;@overrideWidget build(BuildContext context) => Center(child: AnimatedBuilder(animation: animation,builder: (context, child) => SizedBox(height: animation.value,width: animation.value,child: child,),child: child),);
}
// #enddocregion GrowTransitionclass LogoApp extends StatefulWidget {const LogoApp({Key? key}) : super(key: key);@override_LogoAppState createState() => _LogoAppState();
}// #docregion print-state
class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {late Animation<double> animation;late AnimationController controller;@overridevoid initState() {super.initState();controller =AnimationController(duration: const Duration(seconds: 2), vsync: this);animation = Tween<double>(begin: 0, end: 300).animate(controller);controller.forward();}// #enddocregion print-state@overrideWidget build(BuildContext context) => GrowTransition(child: const LogoWidget(),animation: animation,);@overridevoid dispose() {controller.dispose();super.dispose();}
// #docregion print-state
}

如何使用Hero动画?

什么是Hero动画?

在 Flutter中可以用 Hero widget创建这个动画。当 hero 通过动画从源页面飞到目标页面时,目标页面逐渐淡入视野。通常, hero 是用户界面的一小部分,如图片,它通常在两个页面都有。从用户的角度来看, hero 在页面之间“飞翔”。接下来我们一起来学习如何创建Hero动画:

实现标准hero动画

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart' show timeDilation;class PhotoHero extends StatelessWidget {const PhotoHero({Key? key, required this.photo, required this.onTap, required this.width }) : super(key: key);final String photo;final VoidCallback onTap;final double width;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,fit: BoxFit.contain,),),),),);}
}class HeroAnimation extends StatelessWidget {const HeroAnimation({Key? key}) : super(key: key);@overrideWidget build(BuildContext context) {timeDilation = 10.0; // 1.0 means normal animation speed.return Scaffold(appBar: AppBar(title: const Text('Basic Hero Animation'),),body: Center(child: PhotoHero(photo: 'https://raw.githubusercontent.com/flutter/website/master/examples/_animation/hero_animation/images/flippers-alpha.png',width: 300.0,onTap: () {Navigator.of(context).push(MaterialPageRoute<void>(builder: (BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('Flippers Page'),),body: Container(// Set background to blue to emphasize that it's a new route.color: Colors.lightBlueAccent,padding: const EdgeInsets.all(16.0),alignment: Alignment.topLeft,child: PhotoHero(photo: 'https://raw.githubusercontent.com/flutter/website/master/examples/_animation/hero_animation/images/flippers-alpha.png',width: 100.0,onTap: () {Navigator.of(context).pop();}, key: null,),),);}));}, key: null,),),);}
}void main() {runApp(const MaterialApp(home: HeroAnimation()));
}

Hero的函数原型

 const Hero({Key key,@required this.tag,this.createRectTween,this.flightShuttleBuilder,this.placeholderBuilder,this.transitionOnUserGestures = false,@required this.child,}) : assert(tag != null),assert(transitionOnUserGestures != null),assert(child != null),super(key: key);

实现径向hero动画

import 'dart:math' as math;import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart' show timeDilation;class Photo extends StatelessWidget {const Photo({ Key? key, required this.photo, required this.color, required this.onTap }) : super(key: key);final String photo;final Color? color;final VoidCallback onTap;@overrideWidget build(BuildContext context) {return Material(// Slightly opaque color appears where the image has transparency.color: Theme.of(context).primaryColor.withOpacity(0.25),child: InkWell(onTap: onTap,child: LayoutBuilder(builder: (BuildContext context, BoxConstraints size) {return Image.network(photo,fit: BoxFit.contain,);},),),);}
}class RadialExpansion extends StatelessWidget {const RadialExpansion({Key? key,required this.maxRadius,required this.child,}) : clipRectSize = 2.0 * (maxRadius / math.sqrt2),super(key: key);final double maxRadius;final double clipRectSize;final Widget child;@overrideWidget build(BuildContext context) {return ClipOval(child: Center(child: SizedBox(width: clipRectSize,height: clipRectSize,child: ClipRect(child: child,),),),);}
}class RadialExpansionDemo extends StatelessWidget {static const double kMinRadius = 32.0;static const double kMaxRadius = 128.0;static const opacityCurve = Interval(0.0, 0.75, curve: Curves.fastOutSlowIn);const RadialExpansionDemo({Key? key}) : super(key: key);static RectTween _createRectTween(Rect? begin, Rect? end) {return MaterialRectCenterArcTween(begin: begin, end: end);}static Widget _buildPage(BuildContext context, String imageName, String description) {return Container(color: Theme.of(context).canvasColor,child: Center(child: Card(elevation: 8.0,child: Column(mainAxisSize: MainAxisSize.min,children: [SizedBox(width: kMaxRadius * 2.0,height: kMaxRadius * 2.0,child: Hero(createRectTween: _createRectTween,tag: imageName,child: RadialExpansion(maxRadius: kMaxRadius,child: Photo(photo: imageName,onTap: () {Navigator.of(context).pop();}, color: null,),),),),Text(description,style: const TextStyle(fontWeight: FontWeight.bold),textScaleFactor: 3.0,),const SizedBox(height: 16.0),],),),),);}Widget _buildHero(BuildContext context, String imageName, String description) {return SizedBox(width: kMinRadius * 2.0,height: kMinRadius * 2.0,child: Hero(createRectTween: _createRectTween,tag: imageName,child: RadialExpansion(maxRadius: kMaxRadius,child: Photo(photo: imageName,onTap: () {Navigator.of(context).push(PageRouteBuilder<void>(pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {return AnimatedBuilder(animation: animation,builder:(BuildContext context, Widget? child) {return Opacity(opacity: opacityCurve.transform(animation.value),child: _buildPage(context, imageName, description),);});},),);}, color: null,),),),);}@overrideWidget build(BuildContext context) {timeDilation = 5.0; // 1.0 is normal animation speed.return Scaffold(appBar: AppBar(title: const Text('Radial Transition Demo'),),body: Container(padding: const EdgeInsets.all(32.0),alignment: FractionalOffset.bottomLeft,child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,children: [_buildHero(context, 'https://raw.githubusercontent.com/flutter/website/master/examples/_animation/radial_hero_animation/images/chair-alpha.png', 'Chair'),_buildHero(context, 'https://raw.githubusercontent.com/flutter/website/master/examples/_animation/radial_hero_animation/images/binoculars-alpha.png', 'Binoculars'),_buildHero(context, 'https://raw.githubusercontent.com/flutter/website/master/examples/_animation/radial_hero_animation/images/beachball-alpha.png', 'Beach ball'),],),),);}
}void main() {runApp(const MaterialApp(home: RadialExpansionDemo()));
}
  • Animation:是Flutter动画库中的一个核心类,它生成指导动画的值;
  • CurvedAnimation:Animation的一个子类,将过程抽象为一个非线性曲线;
  • AnimationController:Animation的一个子类,用来管理Animation;
  • Tween:在正在执行动画的对象所使用的数据范围之间生成值。例如,Tween可生成从红到蓝之间的色值,或者从0到255;
    • Animation还可以生成除double之外的其他类型值,如:Animation<Color> 或 Animation<Size>
    • Animation对象有状态。可以通过访问其value属性获取动画的当前值;
    • Animation对象本身和UI渲染没有任何关系;
    • forward():启动动画;
    • reverse({double from}):倒放动画;
    • reset():重置动画,将其设置到动画的开始位置;
    • stop({ bool canceled = true }):停止动画;
    • addListener:动画的值发生变化时被调用;
    • addStatusListener:动画状态发生变化时被调用;
    • 显示logo
    • 定义Animation对象
    • 渲染过渡效果
    • tag:[必须]用于关联两个Hero动画的标识;
    • createRectTween:[可选]定义目标Hero的边界,在从起始位置到目的位置的“飞行”过程中该如何变化;
    • child:[必须]定义动画所呈现的widget;

Flutter动画Animation开发指南相关推荐

  1. 动画Animation开发指南-动画基础类

    一. 二.在Flutter中有哪些类型的动画? (可根据官方提供的Flutter gallery中的示例来学习动画) 在Flutter中动画分为两类:基于tween或基于物理的. --补间(Tween ...

  2. 【Flutter】Animation 动画 ( AnimatedBuilder 动画使用流程 | 创建动画控制器 | 创建动画 | 创建动画作用的组件 | 关联动画与组件 | 动画执行 )

    文章目录 ◯.AnimatedBuilder 引入 一.创建动画控制器 二.创建动画 三.创建动画作用的组件 四.创建 AnimatedBuilder 关联动画与组件 五.动画运行 六.完整代码示例 ...

  3. iOS开发:Core Animation编程指南

    关于Core Animation Core Animation是iOS与OS X平台上负责图形渲染与动画的基础设施.Core Animation可以动画视图和其他的可视元素.Core Animatio ...

  4. 【Flutter】Animation 动画 ( AnimatedWidget 动画使用流程 | 创建动画控制器 | 创建动画 | 创建 AnimatedWidget 动画组件 | 动画运行 )

    文章目录 ◯.AnimatedWidget 组件引入 一.创建 AnimatedWidget 动画组件 二.创建动画控制器 三.创建动画 四.动画运行 五.完整代码示例 六.相关资源 Animated ...

  5. 【Flutter】Animation 动画 ( Flutter 动画基本流程 | 创建动画控制器 | 创建动画 | 设置值监听器 | 设置状态监听器 | 布局中使用动画值 | 动画运行 )

    文章目录 一.创建动画控制器 二.创建动画 三.设置值监听器 四.设置状态监听器 五.布局中使用动画值 六.动画运行 七.完整代码示例 八.相关资源 Flutter 动画基本流程 : ① 创建动画控制 ...

  6. 【Flutter】Animation 动画 ( Flutter 动画的核心类 | Animation | CurvedAnimation | AnimationController | Tween )

    文章目录 一.动画的核心类 Animation 二.动画的核心类 CurvedAnimation 三.动画的核心类 AnimationController 四.动画的核心类 Tween 五.相关资源 ...

  7. 【Flutter】Animation 动画 ( Flutter 动画类型 | Flutter 动画的核心类 )

    文章目录 一.Flutter 动画类型 二.Flutter 动画的核心类 三.相关资源 Flutter Animation 动画 : Flutter 动画类型 为 Widget 组件添加动画 为动画添 ...

  8. Flutter开发指南之理论篇:Dart语法04(库,异步,正则表达式)

    总目录 Flutter开发指南之理论篇:Dart语法01(数据类型,变量,函数) Flutter开发指南之理论篇:Dart语法02(运算符,循环,异常) Flutter开发指南之理论篇:Dart语法0 ...

  9. 微信小程序开发之——动画-Animation(3)

    一 概述 wx.createAnimation创建Animation动画实例 wx.createAnimation时,参数的常用属性 动画常见动作 动画执行完成之后导出动画队列(export) 二 w ...

  10. Html5和CSS3开发指南学习

    Html5和CSS3开发指南学习 1. 为什么学习HTML5 跨平台运行--PC/手机/Pad跨平台使用 硬件要求低 flash之外的选择,尤其是手机端开发 2. HTML介绍 2-1 什么是HTML ...

最新文章

  1. 跟着百度学PHP[17]-PHP扩展CURL的POST传输数据
  2. python语言中文社区-python中用中文
  3. new housing price at shanghai
  4. 6-1 水晶报表技术(上)
  5. 计算机程序设计基础试题与答案,2018年4月自考计算机基础与程序设计02275试题及答案.doc...
  6. (转)RabbitMQ学习之exchange总结
  7. SAP License:赛锐信息访谈启示录(一)
  8. mysql新浪微盘_Android62期视频教程全集下载
  9. jquery ajax实例get,jQuery中ajax的get()方法用法实例
  10. 和菜鸟一起学产品之产品经理的三大文档
  11. Hibernate多列作为联合主键(六)
  12. windows常用快捷键与快捷指令
  13. html 搜索历史记录,使用cookie实现历史搜索记录功能
  14. oracle sparc t5-2报价,SPARC T5-2服务器
  15. 为何企业级架构日益盛行?
  16. Vue.directive()的用法和实例
  17. 如何删掉顽固文件和文件夹
  18. 英飞凌微控制器,驱动物联网的关键“大脑”
  19. 你是谁的无价之宝?(真正懂得你价值的人在哪里?)
  20. 哈夫曼树构造(优先队列)

热门文章

  1. 2020-04-27
  2. 第六章(1.8)自然语言处理实战——Jieba中文分词,WordEmbedding的概念和实现
  3. 【游戏开发实战】(完结)使用Unity制作水果消消乐游戏教程(九):使用UGUI显示游戏UI
  4. android 呼吸灯权限_Android 呼吸灯流程分析
  5. 中文网站搜索引擎网站收录提交地址大全
  6. 厦门大学计算机专业录取分数线2019,厦门大学录取分数线2019
  7. 关键点检测---CPN, Cascaded Pyramid Network for Multi-Person Pose Estimation
  8. java实现ftp文件上传失败_用java+ftp实现文件上传的问题?
  9. 数据库建模 : 概念模型 , 逻辑模型和物理模型
  10. TR转TO L_TO_CREATE_TR