为了账号安全,请及时绑定邮箱和手机立即绑定

Flutter 动画之 Animation

标签:
Android

1.前言

1.1:Flutter动画中:

首先要看的是Flutter中动画的几个类之间的关系:


1


主角当然是我们的Animation类了,它可以借助Animatable进行强化
Animatable通过animate函数接收一个Animation对象,再返回Animation对象,这不就是包装吗?
通过Animation对象回调即可获取规律变画的值,进行渲染。这是动画的基本。


1.2:Animation和Animation体系一览

整个Flutter的Animation相比Android还是比较简单的


1



1.3:介绍今天的主角nStarPath

我们通过变动这个函数中的参数让路径动态变化实现动画


1


/// 可以创建一个外接圆半径为[R],内接圆半径半径为[r]的[num]角星路径 Path nStarPath(int num, double R, double r) {   Path path = new Path();   double perDeg = 360 / num;   double degA = perDeg/2/2;   double degB = (360 / (num - 1) - degA) / 2 + degA;   path.moveTo(cos(_rad(degA)) * R, (-sin(_rad(degA)) * R));   for (int i = 0; i < num; i++) {     path.lineTo(         cos(_rad(degA + perDeg * i)) * R, -sin(_rad(degA + perDeg * i)) * R);     path.lineTo(         cos(_rad(degB + perDeg * i)) * r, -sin(_rad(degB + perDeg * i)) * r);   }   path.close();   return path; } double _rad(double deg) {   return deg * pi / 180; } 复制代码

1.4:动画舞台的搭建

对于动画的演示,最好的当然是绘制了,绘制中最好的当然是我的五角星了
感觉创建StatefulWidget的代码开始时基本一致,写了一篇模板解析器
玩转字符串篇--Gradle+代码生成器=懒人必备


1


import 'package:flutter/material.dart'; class AnimPage extends StatefulWidget {   @override   _AnimPageState createState() => _AnimPageState(); } class _AnimPageState extends State<AnimPage>{   @override   Widget build(BuildContext context) {     return Scaffold(       appBar: AppBar(         title: Text("Flutter之旅"),       ),       body: CustomPaint(         painter: AnimView(),       ),       floatingActionButton: FloatingActionButton(         onPressed: () {          //TODO 执行动画         },         child: Icon(Icons.add),       ),     );   } } class AnimView extends CustomPainter {   Paint mPaint;   Paint gridPaint;   AnimView() {     mPaint = new Paint();     gridPaint = Paint()       ..style = PaintingStyle.stroke       ..color = Colors.cyanAccent;     mPaint.color = Colors.deepOrange;   }   @override   void paint(Canvas canvas, Size size) {     canvas.drawPath(gridPath(area: Size(500, 1000)), gridPaint);//绘制网格     canvas.translate(200, 200);     canvas.drawPath(nStarPath(5, 100, 50), mPaint);//绘制五角星   }   @override   bool shouldRepaint(CustomPainter oldDelegate) {     return true;   } } /// 可以创建一个外接圆半径为[R],内接圆半径半径为[r]的[num]角星, Path nStarPath(int num, double R, double r) {   Path path = new Path();   double perDeg = 360 / num;   double degA = perDeg/2/2;   double degB = (360 / (num - 1) - degA) / 2 + degA;   path.moveTo(cos(_rad(degA)) * R, (-sin(_rad(degA)) * R));   for (int i = 0; i < num; i++) {     path.lineTo(         cos(_rad(degA + perDeg * i)) * R, -sin(_rad(degA + perDeg * i)) * R);     path.lineTo(         cos(_rad(degB + perDeg * i)) * r, -sin(_rad(degB + perDeg * i)) * r);   }   path.close();   return path; } double _rad(double deg) {   return deg * pi / 180; } ///创建一个区域是[area],小格边长为[step]的网格的路径 Path gridPath({double step = 20, Size area}) {   Path path = Path();   for (int i = 0; i < area.height / step + 1; i++) {     //画横线     path.moveTo(0, step * i); //移动画笔     path.lineTo(area.width, step * i); //画直线   }   for (int i = 0; i < area.width / step + 1; i++) {     //画纵线     path.moveTo(step * i, 0);     path.lineTo(step * i, area.height);   }   return path; } 复制代码

好了,现在开始Flutter的动画之旅


2.Flutter动画基本使用

这里再贴一下这张Animation使用图:


1



2.1:动画的基本使用:Tween+AnimationController

1.让_AnimPageState类with一下SingleTickerProviderStateMixin
2.使用创建一个AnimationController对象(Animation族)
3.复写SingleTickerProviderStateMixin的dispose方法释放AnimationController对象
4.创建Tween对象(Animatable族)并调用animate方法,生成新的Animation对象
5.监听Animation的变化,获取每次刷新时的值。

class _AnimPageState extends State<AnimPage> with SingleTickerProviderStateMixin {   AnimationController controller;   Animation<double> animation;   @override   void initState() {     super.initState();     controller = AnimationController(////创建 Animation对象         duration: const Duration(milliseconds: 2000), //时长         vsync: this);     var tween = Tween(begin: 25.0, end: 150.0); //创建从25到150变化的Animatable对象     animation = tween.animate(controller); //执行animate方法,生成     animation.addListener(() {       print(animation.value);     });   }   @override   void dispose() {     super.dispose();     controller.dispose(); // 资源释放   }   @override   Widget build(BuildContext context) {     return Scaffold(       //略同...       floatingActionButton: FloatingActionButton(         onPressed: () {           controller.forward(); //执行动画         },        //略同...     );   } } 复制代码

注:有时候为了方便可以连写,关于SingleTickerProviderStateMixin这里不做深究,
但要知道,既然是mixin就是给类附加能力的,其中之一便是dispose()方法

animation = Tween(begin: 25.0, end: 150.0).animate(controller)     ..addListener(() {       print(animation.value);     }); 复制代码

看一下控制台打印结果:从25~150变化的一群数字

---->[控制台打印]---- I/flutter ( 9073): 25.0 I/flutter ( 9073): 26.1205625 I/flutter ( 9073): 27.2418125 I/flutter ( 9073): 28.363125 出处略去n行.... I/flutter ( 9073): 147.20725 I/flutter ( 9073): 148.3288125 I/flutter ( 9073): 149.4503125 I/flutter ( 9073): 150.0 复制代码

2.2:热身运动,看一下Tween下点的轨迹

也是突发奇想,数字在不断变化,这可都是白花花的资源啊,要不秀一个
这个小例子完美的阐述了Tween补间的动画是匀速的


16c02953e9a1ec2d?imageslim


class AnimPage extends StatefulWidget {   @override   _AnimPageState createState() => _AnimPageState(); } class _AnimPageState extends State<AnimPage>     with SingleTickerProviderStateMixin {   AnimationController controller;   Animation<double> animation;   List<Offset> _points=[];//点集   @override   void initState() {     super.initState();     controller = AnimationController(//创建 Animation对象         duration: const Duration(milliseconds: 2000), //时长         vsync: this);     var tween = Tween(begin: 25.0, end: 150.0); //创建从25到150变化的Animatable对象     animation = tween.animate(controller); //执行animate方法,生成     animation.addListener(() {       render(_points,animation.value);     });   }   @override   void dispose() {     super.dispose();     controller.dispose(); // 资源释放   }   @override   Widget build(BuildContext context) {     return Scaffold(       appBar: AppBar(         title: Text("Flutter之旅"),       ),       body: CustomPaint(         painter: AnimView(_points),//入参       ),       floatingActionButton: FloatingActionButton(         onPressed: () {           controller.forward(); //执行动画         },         tooltip: 'Increment',         child: Icon(Icons.add),       ),     );   }   double x=0;   //核心渲染方法,将值加入集合中并渲染   void render(List<Offset> _points, double value) {     _points.add(Offset(x, -value));     x++;     setState(() {//更新组件     });   } } class AnimView extends CustomPainter {   List<Offset> _points;   Paint mPaint;   Paint gridPaint;   AnimView(this._points) {     mPaint = new Paint();     gridPaint = Paint()       ..style = PaintingStyle.stroke       ..color = Colors.cyanAccent;     mPaint..color = Colors.deepOrange..strokeWidth=3;   }   @override   void paint(Canvas canvas, Size size) {     canvas.drawPath(gridPath(area: Size(500, 1000)), gridPaint);     canvas.translate(200,200);     canvas.drawCircle(Offset(0, 0), 2.5, gridPaint..color=Colors.black..style=PaintingStyle.fill);     _drawStar(canvas,_points);   }   @override   bool shouldRepaint(CustomPainter oldDelegate) {     return true;   }   void _drawStar(Canvas canvas, List<Offset> pos) {     canvas.drawPoints(PointMode.lines, pos, mPaint);   } } ///创建一个区域是[area],小格边长为[step]的网格的路径 Path gridPath({double step = 20, Size area}) {   Path path = Path();   for (int i = 0; i < area.height / step + 1; i++) {     //画横线     path.moveTo(0, step * i); //移动画笔     path.lineTo(area.width, step * i); //画直线   }   for (int i = 0; i < area.width / step + 1; i++) {     //画纵线     path.moveTo(step * i, 0);     path.lineTo(step * i, area.height);   }   return path; } 复制代码

2.3:创建星星的描述类和绘制

三个属性,外接圆半径,内接圆半径和角数

class Star{   int num;   double R;   double r;   Star(this.num,this.R,this.r); } ---->[AnimView类]---- class AnimView extends CustomPainter {   Star _star;   AnimView(this._star) {     //略同...   }   @override   void paint(Canvas canvas, Size size) {     //略同...     _drawStar(canvas,_star);   }   //绘制星星   void _drawStar(Canvas canvas, Star star) {     canvas.drawPath(nStarPath(star.num, star.R, star.r), mPaint);   } } ---->[_AnimPageState类]---- class _AnimPageState extends State<AnimPage>     with SingleTickerProviderStateMixin {        Star _star;   @override   void initState() {     _star=Star(5, 100, 50);       //略同...   }   @override   Widget build(BuildContext context) {         //略同...       body: CustomPaint(         painter: AnimView(_star), 复制代码

2.3:动态更新

只需要在刷新的时候更改五角星的属性就行了,下面就是外接圆半径25~150变化


16bff639f2ec1e42?imageslim


animation.addListener(() {   render(_star,animation.value); } //核心渲染方法 void render(Star star, double value) {   star.R=value;   setState(() {//更新组件   }); } 复制代码

2.4:int数据的动画:IntTween

Tween是两个double类型的数字在一定的时间内的均匀变化
那int该肿么办?Tween之下有二十来个孩子用于不同的对象变化
其一便是IntTween,这里让星星的角数从5~100不断变化形成动画


16c02a33fc78d126?imageslim


class _AnimPageState extends State<AnimPage>     with SingleTickerProviderStateMixin {   Animation<int> animation;//改成int泛型  //略同...   @override   void initState() {   //略同...     var intTween = IntTween(begin: 5, end: 100);    //略同...   }   //核心渲染方法   void render(Star star, int value) {     star.num=value;     setState(() {//更新组件     });   } } 复制代码

实现起来还是比较简单的


2.5:颜色变化: ColorTween

顾名思义,匀速改变颜色呗,思路是一致的,这里先给Star描述类价格color字段
在Canvas绘制时使用Satr的颜色,这样在刷新时就会呈现颜色渐变


16c02ae5bad8d0b0?imageslim


class Star{   //略同...   Color color;   Star(this.num,this.R,this.r,this.color); } class _AnimPageState extends State<AnimPage>     with SingleTickerProviderStateMixin {   Animation<Color> animation;  //略同...   @override   void initState() {     _star=Star(5, 100, 60,Colors.red);     //略同...     var colorTween = ColorTween(begin: Colors.red, end: Colors.yellow);      //创建从红到黄变化的Animatable对象   }      //核心渲染方法   void render(Star star, Color value) {     star.color=value;     setState(() {//更新组件     });   } } ---->[AnimView:绘制时使用颜色]---- void _drawStar(Canvas canvas, Star star) {   canvas.drawPath(nStarPath(star.num, star.R, star.r), mPaint..color=star.color); } 复制代码

3.让动画更有动感:CurveTween

看名字是曲线补间,也就是运动不再是匀速的,可以自己设计。

3.1:看一下CurveTween的源码

需要一个curve属性,对应的是Curve对象。
Curve为抽象类,有一个四入参的子类Cubic,去吧,皮卡丘就决定是你了。

---->[CurveTween]---- class CurveTween extends Animatable<double> {   CurveTween({ @required this.curve })     : assert(curve != null);   Curve curve;    ---->[Curve]---- @immutable abstract class Curve { ---->[Curve]---- class Cubic extends Curve {   const Cubic(this.a, this.b, this.c, this.d) 复制代码

3.2:关于曲线参数的获取

记得掘金的头像可以转,Chrome浏览器里有个小功能,在调试面板里
看来一下有个lazy的样式下的translation,点开可以调试曲线,获取四个值


16c02bdadccadb6c?imageslim


用刚才的画点方法看了一下数据的变动情况


16c02d2280e9deb6?imageslim



3.3:代码操作

根据包装设设计模式的思想,CurveTween可以强化Animation拥有从0~1的曲线,
然后再送到Tween中进行补间,让其在两个数的范围内具有曲线补间能力


16c02db6b9ee085c?imageslim


controller = AnimationController(//创建 Animation对象     duration: const Duration(milliseconds: 2000), //时长     vsync: this);      var curveTween = CurveTween(curve:Cubic(0.96, 0.13, 0.1, 1.2));//创建curveTween var tween=Tween(begin: 50.0, end: 100.0); animation = tween.animate(curveTween.animate(controller)); animation.addListener(() {   render(_star,animation.value); }); 复制代码

另外,Curves中也定义了41个常用的Curve,来方便使用,大家可以试试


4.动画的监听和动画序列

4.1:运动状态:AnimationStatus

相像一下,一个百米跑道标注着刻度,哨声一响,你开始跑

enum AnimationStatus {   dismissed,//在正在开始时停止了?跌倒在起跑线上   forward,//运动中   reverse,//跑到终点,再跑回来的时候   completed,//跑到终点时 } 复制代码

4.2:为Animation添加监听

通过Animation#addStatusListener可以回调AnimationStatus对象


16c02ef840b4ea97?imageslim


animation.addStatusListener((status){   switch(status){     case AnimationStatus.completed:       controller.reverse();//反向       break;     case AnimationStatus.forward:       break;     case AnimationStatus.reverse:       _star.color=randomRGB();       break;     case AnimationStatus.dismissed:       controller.forward();       break;   } }); 复制代码

4.3:最后说一下序列动画

找了好一会都没有发现多值的api,只有start和end两个值
然后翻译一下源码,看到还有个TweenSequence,顾名思义,序列动画
现在重新写个组件叫FlutterText,拥有颤动效果的文字


16c045b25189bc96?imageslim


class FlutterText extends StatefulWidget {   var str;   var style;   FlutterText(this.str, this.style);   _FlutterTextState createState() => _FlutterTextState(); } class _FlutterTextState extends State<FlutterText>     with SingleTickerProviderStateMixin {   Animation<double> animation;   AnimationController controller;   initState() {     super.initState();     controller = AnimationController(         duration: const Duration(milliseconds: 1000), vsync: this);          animation = TweenSequence<double>([//使用TweenSequence进行多组补间动画       TweenSequenceItem<double>(tween: Tween(begin: 0, end: 15), weight: 1),       TweenSequenceItem<double>(tween: Tween(begin: 15, end: 0), weight: 2),       TweenSequenceItem<double>(tween: Tween(begin: 0, end: -15), weight: 3),       TweenSequenceItem<double>(tween: Tween(begin: -15, end: 0), weight: 4),     ]).animate(controller)       ..addListener(() {         setState(() {});       })       ..addStatusListener((s) {         if (s == AnimationStatus.completed) {           setState(() {});         }       });     controller.forward();   }   Widget build(BuildContext context) {     var result = Transform(       transform: Matrix4.rotationZ(animation.value * pi / 180),       alignment: Alignment.center,       child: Text(         widget.str,         style: widget.style,       ),     );     return result;   }   dispose() {     controller.dispose();     super.dispose();   } } 复制代码

这样,Animation基本用法就说完了,还有几个类就不一一介绍了,基本使用都差不多
关于动画效果,是一个永远也无法满足的深渊,它无法言尽。
一张经典的画作重要的不是画笔,而是握笔的人,你的动画属于你。




点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消