86

使用Flutter实现58同城中的加载动画

 3 years ago
source link: https://www.tuicool.com/articles/bIvEjiY
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

在应用中执行耗时操作时,为了避免界面长时间等待造成假死的现象,往往会添加一个加载中的动画来提醒用户,在58同城中也不例外,而且我们并没有使用系统默认的加载动画,而是制作了一个具有58特色的加载动画。

在本篇文章中,给大家分享下笔者使用Flutter实现58同城中加载动画的过程。先看一下加载动画的效果:

mQ3aumM.gif

动画效果乍看比较复杂,难以看出端倪,其实我们可以先调慢动画的速度,这样能够比较清晰地分析出动画的流程。

动画的流程

动画由两个圆弧的动效组成,两个圆弧的起始点角度和扫过的弧度随着时间规律变化。仔细观察会发现,两个圆弧的动效其实是一样的,只不过起始位置是不一样的。我们先看下外部大圆弧的运动规律。

大圆弧从x轴正方向开始运动,按照动画的运动规律,可以将动画分为三个阶段:

第一阶段:圆弧起点的在x轴正方向,终点的角度x轴正方向开始向下逐渐增大,直到终点到达y轴负方向位置,最终圆弧扫过的角度为180度。

第二阶段:圆弧扫过的角度保持在180度,起点和终点一起顺时针旋转,直到旋转180度后终点到达x轴正方向。

第三阶段:圆弧的终点保持在x轴正方向,起点顺时针旋转,直到起点也到达x轴正方向,此时完成一个完整的动画。接下来继续重复动画的第一阶段,组成一个连贯的动画。

分析完动画的流程,思路就很清晰了,我们按照动画流程把动画拆分成三部分,通过对圆弧的起点、终点和扫过角度的变换,组合成一个完整的动画,然后不断地重复,最后就变成了一个加载中的动画效果。

接下来开始写代码实现。

由于动画是由一个圆弧不断变化组成的,如果使用Android,我们很自然的想到可以使用Canvas来进行圆弧的绘制,然后根据时间的变化不停地重新绘制圆弧,从而实现动画效果。那么在Flutter中是否也存在Canvas呢,答案是肯定的,Flutter和Android一样,也存在Canvas。

Flutter中的Canvas

Flutter中使用 CustomPainter 类在Canvas上进行绘制,该类包含一个  paint() 方法,该方法提供了一个Canvas对象,可以用来绘制各种图形。

不过在Flutter中一切皆是Widget,而承载Canvas功能的Widget是 CustomPaint 类。  CustomPaint 包含一个painter属性,用来指定进行绘制的  CustomPainter ,源码如下:

Flutter中的Canvas和Android类似,提供了一系列的API用来绘制点、线、圆形、正方形等,而且API很类似,对比一下Flutter与Android中Canvas的常见API(具体的参数列表请参考文档和源码,篇幅有限不再一一列出):

Android Flutter

drawPoint( )

drawPoints()

drawPoints() 线

drawLine( )

drawLines()

drawLine() drawCircle() drawCircle() 椭圆 drawOval() drawOval() 圆弧 drawArc() drawArc() 矩形 drawRect() drawRect() Path drawPath() drawPath() 图片 drawBitmap() drawImage() 文字 drawText() drawParagraph() 变换

save( )

restore()

save()

restore()

要绘制动画中的圆弧,应该使用 drawArc() 方法来实现, 这里需要注意的是drawArc()方法的参数:startAngle和sweepAngle的单位是弧度(180度等于π弧度)

具体来看一下 Canvas.drawArc() 方法的参数列表:

在Canvas的一系列方法中会发现一个熟悉的名称:Paint,与Android类似,Flutter中的Paint类也是用来描述画笔的。

Paint类

Paint类位于 dart.ui 库中,Paint类保存了画笔的颜色、粗细、是否抗锯齿、着色器等属性。

下面简单的介绍下几个常用的属性:

属性说明:

  • color:Color类型,设置画笔的颜色。

  • strokeWidth:double类型,设置画笔的粗细。

  • style:PaintingStyle枚举类型,设置画笔的样式, PaintingStyle.stroke 为描边,  PaintingStyle.fill 为填充。

  • isAntiAlias:bool类型,设置是否抗锯齿,true为开启抗锯齿。

  • shader:Shader类型,着色器,一般用来绘制渐变效果,可以使用 LinearGradient 、  RadialGradient 、  SweepGradient 等。

  • strokeCap:StrokeCap枚举类型,设置线条两端点的样式, StrokeCap.butt 为无(默认值),  StrokeCap.round 为圆形,  StrokeCap.square 为方形。

  • strokeJoin:StrokeJoin枚举类型,设置线条交汇处的样式, StrokeJoin.miter 为锐角,  StrokeJoin.round 为圆弧,  StrokeJoin.bevel 为斜角,可以参考下图方便理解:

RrYj2aQ.png!web

熟悉了Canvas和Paint的使用之后,就能够绘制出加载动画的圆弧了。当然,只是绘制出圆弧并没有什么用,主要是怎么让圆弧动起来。

Flutter中的动画

想要让圆弧动起来,我们需要使用到Flutter的动画。下面先来介绍下Flutter中动画的实现。

Flutter中的动画相关的类主要有以下几个:

  • Animation: 动画 的核心类,是一个抽象类。 用来生成动画执行过程中的插值,输出的结果可以是线性或曲线的,Animation对象与UI渲 染没有任何关系。

  • AnimationController: 动画的管理类,继承自  Animation<double> 默认情况下在给定的时间范围内线性生成从0.0到1.0的值。

    AnimationController对象需要传递一个vsync参数,它接收一个TickerProvider类型的对象,主要职责是创建Ticker。 Flutter应用在启动时会绑定一个SchedulerBinding,可以给每一次屏幕刷新添加回调,Ticker就是通过SchedulerBinding来添加屏幕刷新的回调,当屏幕刷新时,会通知到绑定的Ticker回调。 假如动画的UI不在当前屏幕,比如锁屏时,锁屏后屏幕停止刷新,不会通知SchedulerBinding,Ticker也就不会触发,这样就能够防止屏幕外的动画消耗不必要的资源。

  • CurvedAnimation: 非线性动画类,继承自  Animation<double> CurvedAnimation可以使用curve属性指定曲线函数Curve,类似Android动画的插值器,Flutter中已经实现了许多常用的曲线,在Curves类中可以找到,比如Curves.linear、Curves.decelerate、Curves.ease。 也可以继承Curve类重写  transform()  方法来实现自定义的曲线函数。

  • Tween: 补间值的生成类,继承自  Animatable<T>

    由于AnimationController的值范围默认为0.0到1.0,如果需要不同的范围或数据类型,可以使用Tween指定动画值的范围。 Tween不仅能返回double类型的值,还有IntTween、ColorTween、SizeTween等各种返回不同数据类型的子类。

    使用Tween对象需要调用  animate()  方法,传入AnimationController对象,该方法会返回一个Animation,这样就可以获取到动画的插值了。
  • AnimatedBuilder: 用于构建动画的Widget,将动画和要执行动画的Widget关联起来,继承关系为AnimatedBuilder → AnimatedWidget → StatefulWidget。

分析上面列出的源码,AnimatedWidget是一个StatefulWidget。当AnimatedWidget关联的_AnimatedState初始化时,会注册动画的监听函数_handleChange,_handleChange监听函数中又调用了setState()方法,即动画插值每次改变时都会调用 build() 方法。 _AnimatedState.build() 方法中又调用了 AnimatedWidget.build() 方法,在AnimatedBuilder中实现了 AnimatedWidget.build() 方法:调用属性builder生成Widget,最终实现了动画与Widget的绑定

加载动画的实现

了解了Flutter的动画后,再结合之前对加载动画流程的分析,加载动画可分成三个阶段,我们可以依赖Tween类,指定值的范围从0.0到3.0变化,当然也可以只使用AnimationController,指定lowerBound和upperBound的值分别为0.0和3.0。这里之所以不使用CurvedAnimation,是因为加载动画的圆弧是线性变化的,不存在加速减速,没有必要使用。

大圆弧能够实现了,我们再来看内部的小圆弧,仔细观察会发现小圆弧的变化规律与大圆弧完全一致,只不过小圆弧的起始位置在x轴负方向,与大圆弧正好相差180度,也就是π弧度。在绘制大圆弧的同时,可以很轻松的计算出小圆弧的起点的角度(即大圆弧起点的角度+π弧度)。

至此整个动画的实现思路就清晰了:

  1. 自定义加载动画的Widget,继承自CustomPaint类。

  2. 使用AnimationController、Tween创建动画,动画的值范围从0.0到3.0线性变化,并且设置动画重复执行。 动画插值每递增1.0代表动画执行的一个阶段。

  3. 继承CustomPainter类,实现paint()方法绘制圆弧。 根据动画的插值判断当前属于动画的哪个阶段,再计算出圆弧的起点、扫过的角度,绘制出两个圆弧。

下面是实现加载动画的关键代码:

总结

Flutter的Canvas、Paint与Android的API非常类似,基本的思路也一致,对于Android同学比较容易掌握。

Flutter中动画的实现相较于Android逻辑更加清晰简单,方便易用。AnimatedBuilder类巧妙的将UI与动画整合在一起,把UI和动画职责分离,这种思路值得学习。Flutter中的动画还有路由过渡动画、Hero动画、切换动画组件AnimatedSwitcher等,有需要的同学可以查找相关资料。

如果大家需要定制一些个性化的加载动画,推荐一个GitHub的开源项目:flutter_spinkit,这个插件提供了很多种常用的 加载动画 效果。

RBJFnaB.jpg!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK