简单介绍

昨天在简书上看到一篇文章。介绍了一个载入动画的实现过程
一款Loading动画的实现思路(一)
仅仅可惜原动画是IOS上制作的。而看了一下。作者的实现思路比較复杂,于是趁着空暇写了一个Android版本号。这篇文章将给大家介绍一下实现过程。

首先让我们来看一下动画效果

动画结构分析

从上面的gif图中能够看到,这个载入动画有成功,失败两种状态,因为Gif速度比較快,我们再来分别看一张慢图

1、成功状态载入动画


成功动画的状态转移描写叙述例如以下:

1、载入过程。画蓝色圆环,当进度为100%时,圆环完毕
2、从右側抛出蓝色小方块。小方块沿着曲线到达圆环正上方
3、蓝色小方块下落。下落过程中,逐渐变长。当方块与圆圈接触时,进入圆环的部分变粗。同一时候圆环逐渐被挤压,变成椭圆形
4、方块底端到达圆环中心后,发出三个分叉向圆周延伸,同一时候椭圆被撑大。逐渐恢复回圆形
5、圆环变绿色画出绿色勾√

整个过程能够说是比較复杂的,甚至对照原动画。事实上另一些细节我没有去实现。只是接下来我为大家逐个分解每一个过程是怎么实现的。并且并不难理解

每一个小过程组合起来,就是一款炫酷动画,希望大家都有信心去了解它。

自己定义View,依据进度绘制圆形

首先我们来实现第一个过程。圆环的绘制。
在动画效果中。圆环的完整程度。是依据实际的进度来衡量的,当载入完毕。整个圆就画好了。
所以我们自己定义一个View控件。在其提供了一个setProgress()方法来给使用者设置进度

public class SuperLoadingProgress extends View {/*** 当前进度*/private int progress = 0;/*** 最大进度*/private static final int maxProgress = 100;....public void setProgress(int progress) {this.progress = Math.min(progress,maxProgress);postInvalidate();if (progress==0){status = 0;}}...}

有了这个进度以后,我们就调用postInvalidate()去让控件重绘,事实上就是触发了其ondraw()方法。然后我们就再ondraw()方法里面。绘制圆弧
对于圆弧的绘制。相信大家都不会陌生(陌生也没有关系。因为非常easy),仅仅要调用一个canvas.drawArc()方法就能够了。
可是我要细致观察这里的圆形效果。在单独来看三张图

圆弧起始状态

圆弧运动状态

圆弧终于状态

能够看到,首先圆弧有一定的起始角度。我们知道。在Android坐标系中,0度事实上是指水平向右開始的
也就是起点的起始角度。事实上是-90度终点的起始角度,事实上-150度

而整个过程中。
起点:-90度,逆时针旋转270度。最后回到0度位置
终点:-150度。与起点相差60度。最后相差360度,与起点重合

所以当progress=1。也就是动画完毕时。起点会减去270度,那么相应每一个progress
起点的位置应该是

-90-270*progress

当progress=1,终点和起点相差360度。而一開始就相差60度,所以整个过程就是多相差了300度,那么相应每一个progress。终点和起点应该相差

-(60+precent*300)

依据上面的结论。我们得到圆弧的详细绘制方式例如以下:

    /*** 起始角度*/private static final float startAngle = -90;@Overrideprotected void onDraw(Canvas canvas) {...float precent = 1.0f*progress/maxProgress;//当前完毕百分比//mRectF是代表整个view的范围canvas.drawArc(mRectF, startAngle-270*precent, -(60 + precent*300), false, circlePaint);}

圆环完毕,抛出小方块

在圆环绘制完毕以后,会抛出一个小方块。小方块沿曲线运动到圆环正上方,实际整个曲线,是一段圆弧
我们来看下图

方块运动状态

运动状态分析图


从图中能够看出,方块运动的终点,距离圆心为2R
如果运动轨迹是某个圆的一段弧,那么依据勾股定理有例如以下方程

(X+R)^2 + (2R)^2 = (X+2R)^2

解得X=R/2(事实上也非常easy解,就是勾三股四玄五)
如果我们希望方块在500ms内从起点运动到终点。那么我们就须要提供一个计时器,告诉我们如今运动了多少毫秒。然后依据这个时间,计算出方块当前位置
另外,因为方块本身有一定的长度。因此方块也有自己的起始端和末端

可是两者的运动轨迹是一样的,仅仅是先后不同

        //抛出动画endAngle = (float) Math.atan(4f/3);mRotateAnimation = ValueAnimator.ofFloat(0f, endAngle*0.9f );mRotateAnimation.setDuration(500);mRotateAnimation.setInterpolator(new AccelerateDecelerateInterpolator());mRotateAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {curSweepAngle = (float) animation.getAnimatedValue();//运动了多少角度invalidate();}});

每次获得新角度。我们就去又一次绘制方块的位置:

/*** 抛出小方块* @param canvas*/private void drawSmallRectFly(Canvas canvas){canvas.save();canvas.translate(radius / 2 + strokeWidth, 2 * radius + strokeWidth);//将坐标移动到大圆圆心float bigRadius = 5*radius/2;//大圆半径//方块起始端坐标float x1 = (float) (bigRadius*Math.cos(curSweepAngle));float y1 = -(float) (bigRadius*Math.sin(curSweepAngle));//方块末端坐标float x2 = (float) (bigRadius*Math.cos(curSweepAngle+0.05*endAngle+0.1*endAngle*(1-curSweepAngle/0.9*endAngle)));//float y2 = -(float) (bigRadius*Math.sin(curSweepAngle+0.05*endAngle+0.1*endAngle*(1-curSweepAngle/0.9*endAngle)));canvas.drawLine(x1, y1, x2, y2, smallRectPaint);//小方块。事实上是一条直线canvas.restore();        canvas.drawArc(mRectF, 0, 360, false, circlePaint);//蓝色圆环}

抛出完毕。方块下落

能够说下落过程,是整个动画中最复杂的过程了。包含方块下落。圆环挤压,方块变粗三个过程,整个过程,从方块下落開始,到方块底部究竟圆心

首先是方块的下落,这个easy理解,方块会逐渐变长。因为在同样时间内,起始端和末端运动的距离不一样
我们拿末端作为样例,这里要使用到一个知识。就是P**ath路径类**
这是Android提供的一个类。代表我们制定的一段路径实例。对于方块末端来说,其运动的路径就是从顶部,到圆心

    Path downPath1 = new Path();//起始端路径downPath1.moveTo(2*radius+strokeWidth,strokeWidth);downPath1.lineTo(2 * radius+strokeWidth, radius+strokeWidth);Path downPath2 = new Path();//末端路径downPath2.moveTo(2 * radius+strokeWidth, strokeWidth);downPath2.lineTo(2 * radius+strokeWidth, 2 * radius+strokeWidth);

那么问题来了,有了运动路径以后,我们希望有动画。起始就是希望,我们给定一个动画时间,我们能够获得在这段时间的某个点上,起始端/末端运动到路径的哪个位置
那么有了路径以后,我们能不能获得路径上的随意一个位置呢?答案是使用PathMeasure类

可能有很多朋友对这个类不熟悉,能够參考一些文章。或者看看官方API介绍
看PathMeasure大展身手

我们首先来看,怎么初始化一个PathMeasure,非常easy,传入一个Path对象就可以,false表示不闭合这个路径

    downPathMeasure1 = new PathMeasure(downPath1,false);downPathMeasure2 = new PathMeasure(downPath2,false);

因为动画有一定时间。我们又须要一个计时器

    //下落动画        mDownAnimation = ValueAnimator.ofFloat(0f, 1f );mDownAnimation.setDuration(500);mDownAnimation.setInterpolator(new AccelerateInterpolator());mDownAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {downPrecent = (float) animation.getAnimatedValue();invalidate();}});

接下来是使用PathMeasure获得下落过程中,起始端和末端的坐标

    //下落方块的起始端坐标float pos1[] = new float[2];float tan1[] = new float[2];downPathMeasure1.getPosTan(downPrecent * downPathMeasure1.getLength(), pos1, tan1);//下落方块的末端坐标float pos2[] = new float[2];float tan2[] = new float[2];downPathMeasure2.getPosTan(downPrecent * downPathMeasure2.getLength(), pos2, tan2);

getPosTan()方法,第一个參数是指想要获得的路径长度。比如你设置的Path长度为100
那么你传入60,就会获得长度为60时的终点坐标(文字真的表达不好/(ㄒoㄒ)/~~,大家能够去看API)

依据起始端和末端的坐标*。我们绘制一条直线。就是小方块啦!

方块下落,进入圆内部分变粗。圆被挤压变形

接下来要处理一个更加复杂的问题,就是进入圆环中的方块部分,要变粗
为了解决问题。我们就须要分辨方块哪部分在圆内,哪部分在圆外,这个推断起来本身就非常麻烦。况且,圆环还会被压缩!也就是园内圆外,没有一个固定的分界点。

怎么区分圆内圆外呢?我决定自己推断太麻烦了,后来想到一个办法,推断交集!
我们知道,Android提供了API。让我们能够推断两个Rect是否相交,也能够获得它们的相交部分(也就是重合部分),还能够获得非重合部分。

如果我把方块看成是一个矩形。圆环看成一个矩形,那么问题就简单了,我就能够调用API计算出进入圆内的部分,和在圆外的部分了。
例如以下图:

我们知道,事实上圆/椭圆。都是依靠一个矩形确定的。在这个动画中,我们希望圆被挤压成椭圆,终于缩放比例为0.8,大概是这种

利用前面提到的计时器,我们能够依据当前时间。知道圆被挤压的比例。实现挤压效果

    //椭圆形区域Rect mRect = new Rect(Math.round(mRectF.left),Math.round(mRectF.top+mRectF.height()*0.1f*downPrecent),Math.round(mRectF.right),Math.round(mRectF.bottom-mRectF.height()*0.1f*downPrecent));

这样,我们就有了代表椭圆的矩形。因为在一步中,我们知道了小方块的起始端和末端坐标。我们能够使这个两个坐标,分别向左右偏移一定距离,从而获得4个坐标。来创建矩形。
最后,我们直接利用两个矩形,取交集和非交集,详细实现例如以下:

        //非交集Region region1 = new Region(Math.round(pos1[0])-strokeWidth/4,Math.round(pos1[1]),Math.round(pos2[0]+strokeWidth/4),Math.round(pos2[1]));region1.op(mRect, Region.Op.DIFFERENCE);drawRegion(canvas, region1, downRectPaint);//交集Region region2 = new Region(Math.round(pos1[0])-strokeWidth/2,Math.round(pos1[1]),Math.round(pos2[0]+strokeWidth/2),Math.round(pos2[1]));boolean isINTERSECT = region2.op(mRect, Region.Op.INTERSECT);drawRegion(canvas, region2, downRectPaint);

Region是Android提供的,用于处理区域运算问题的一个类,使用这个类,我们能够非常方便进行Rect交集补集等运算,不了解的朋友,查看API

最后绘制这两个区域,并且加上一个推断。就是这个两个矩形是否有相交,如果没有,那么圆环就不用被挤压。直接绘制圆环就可以。

    //椭圆形区域if(isINTERSECT) {//如果有交集float extrusionPrecent = (pos2[1]-radius)/radius;RectF rectF = new RectF(mRectF.left, mRectF.top + mRectF.height() * 0.1f * extrusionPrecent, mRectF.right, mRectF.bottom - mRectF.height() * 0.1f * extrusionPrecent);//绘制椭圆canvas.drawArc(rectF, 0, 360, false, circlePaint);}else{canvas.drawArc(mRectF, 0, 360, false, circlePaint);//绘制圆}

下落完毕。绘制三叉

对于三叉的绘制,就没有什么特别的了,事实上三叉就是三条Path路径,我们用相似前面的做法,利用一个计时器,三个Path,相应三个PathMeasure,就能够动态绘制出路径了。

    /*** 绘制分叉* @param canvas*/private void drawFork(Canvas canvas) {float pos1[] = new float[2];float tan1[] = new float[2];forkPathMeasure1.getPosTan(forkPrecent * forkPathMeasure1.getLength(), pos1, tan1);float pos2[] = new float[2];float tan2[] = new float[2];forkPathMeasure2.getPosTan(forkPrecent * forkPathMeasure2.getLength(), pos2, tan2);float pos3[] = new float[2];float tan3[] = new float[2];forkPathMeasure3.getPosTan(forkPrecent * forkPathMeasure3.getLength(), pos3, tan3);canvas.drawLine(2 * radius+strokeWidth, radius+strokeWidth, 2 * radius+strokeWidth, 2 * radius+strokeWidth, downRectPaint);canvas.drawLine(2 * radius+strokeWidth, 2 * radius+strokeWidth, pos1[0], pos1[1], downRectPaint);canvas.drawLine(2 * radius+strokeWidth, 2 * radius+strokeWidth, pos2[0], pos2[1], downRectPaint);canvas.drawLine(2 * radius+strokeWidth, 2 * radius+strokeWidth, pos3[0], pos3[1], downRectPaint);//椭圆形区域RectF rectF = new RectF(mRectF.left, mRectF.top + mRectF.height() * 0.1f * (1-forkPrecent), mRectF.right, mRectF.bottom - mRectF.height() * 0.1f * (1-forkPrecent));canvas.drawArc(rectF, 0, 360, false, circlePaint);}

最后,还要记得将椭圆还原成圆。事实上就是压缩的逆过程
效果例如以下:

绘制绿色勾√

绿色勾的绘制事实上也和上面的做法相似,须要一个计时器,一个Path,相应的PathMeasure就可以
勾的路径例如以下:

//初始化打钩路径Path tickPath = new Path();tickPath.moveTo(1.5f * radius+strokeWidth, 2 * radius+strokeWidth);tickPath.lineTo(1.5f * radius + 0.3f * radius+strokeWidth, 2 * radius + 0.3f * radius+strokeWidth);tickPath.lineTo(2*radius+0.5f * radius+strokeWidth,2*radius-0.3f * radius+strokeWidth);tickPathMeasure = new PathMeasure(tickPath,false);

最后将路径动态绘制出现,到这里大家都非常熟悉这个做法了。可是这里我使用了另外一个方法,这种方法能够依据进度。直接返回当前路径成一个Path对象

/*** 绘制打钩* @param canvas*/private void drawTick(Canvas canvas) {Path path = new Path();/** On KITKAT and earlier releases, the resulting path may not display on a hardware-accelerated Canvas. * A simple workaround is to add a single operation to this path, such as dst.rLineTo(0, 0).*/tickPathMeasure.getSegment(0, tickPrecent * tickPathMeasure.getLength(), path, true);//该方法,能够获得整个路径的一部分path.rLineTo(0, 0);//解决Android本身的一个bugcanvas.drawPath(path, tickPaint);//绘制出这一部分canvas.drawArc(mRectF, 0, 360, false, tickPaint);}

于是我们在一定时间内。逐渐获得勾这个路径的一部分。知道获得整个勾,并将其绘制出来!
终于效果例如以下:

写在最后

本篇文章。首先介绍成功载入的动画实现过程下一篇文章将会接着介绍载入失败过程的实现。
通过这篇文章,我们应该熟悉了Path,PathMeasure,Region等一系列API,利用这些API。我们能够方便得绘制出路径效果。
每一个步骤组合起来,就是一个好看的,复杂的动效。对于API不熟悉的朋友,建议用到的时候去查官方文档,或者看看其它朋友的一些介绍基础的文章。

最后,提供源代码下载地址和github地址,欢迎大家下载和star

转载于:https://www.cnblogs.com/gavanwanggw/p/7253540.html

一款炫酷Loading动画--载入成功相关推荐

  1. 一款炫酷Loading动画--加载失败

    简介 上一篇文章一款炫酷Loading动画–加载成功,给大家介绍了成功动画的绘制过程,这篇文章将接着介绍加载失败特效的制作. 相比成功动画,有了前面的经验,失败动画的过程就显得比较简单了. 动画结构分 ...

  2. 一款炫酷Loading动画--加载成功

    简介 昨天在简书上看到一篇文章,介绍了一个加载动画的实现过程 一款Loading动画的实现思路(一) 只可惜原动画是IOS上制作的,而看了一下,作者的实现思路比较复杂,于是趁着空闲写了一个Androi ...

  3. 10款炫酷的HTML5动画特效,附源码

    HTML5确实非常强大,很多时候我们可以利用HTML5中的新技术实现非常炫酷效果时,这些效果也非常消耗电脑的CPU,但是这些HTML5效果确实能给用户带来不一样的用户体验. 今天我要跟大家分享一些HT ...

  4. 10款超炫html5游戏,10款炫酷的HTML5动画特效,附源码

    HTML5确实非常强大,很多时候我们可以利用HTML5中的新技术实现非常炫酷效果时,这些效果也非常消耗电脑的CPU,但是这些HTML5效果确实能给用户带来不一样的用户体验. 今天我要跟大家分享一些HT ...

  5. Android 炫酷动画APP,21 款炫酷动画开源框架,照亮你的APP

    原标题:21 款炫酷动画开源框架,照亮你的APP 2017年安卓巴士全球开发者论坛-上海站 前言 最近对应用的UI视觉效果突然来了兴致,所以找了一些合适开源控件,这样更加省时,再此分享给大家,希望能对 ...

  6. 一款炫酷的相册动画合集【源码分享】

    这是一款炫酷的相册动画合集,集合了粒子.雪花.气泡.蝴蝶心形路径.星星.相册翻页等效果,有需要的可以点击下方名称链接下载. 效果图 炫酷的相册动画合集 整理不易,欢迎大家交流学习.

  7. 【CSS3】多款炫酷鼠标悬停图文动画效果

    演示效果: HTML代码如下: <!doctype html> <html lang="zh"> <head><meta charset= ...

  8. 我用 Python 写了一款炫酷音乐播放器,想听啥随便搜!

    作者:Dragon少年 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/hhladminh ...

  9. WPF实现炫酷Loading控件

    原文: WPF实现炫酷Loading控件 Win8系统的Loading效果还是很不错的,网上也有人用CSS3等技术实现,研究了一下,并打算用WPF自定义一个Loading控件实现类似的效果,并可以让用 ...

最新文章

  1. pip(pip3)安装依赖库失败
  2. DDos游戏行业受攻击最多
  3. java web中中文乱码问题汇总
  4. 淘宝+天猫+闲鱼卖的翻新货店家汇总(持续更新)
  5. 游戏服务器架构-设计模式之发布订阅模式
  6. mysql 按照两个字段之和进行排序
  7. 人工智能这么火,可你真的会用 TensorFlow?
  8. 中英金融科技论坛:监管科技增长较快 区块链等新技术挑战监管能力
  9. ubuntu和windows双系统启动顺序的修改
  10. 普中V2 7人多数表决器 51单片机 仿真 proteus
  11. android 测试手机屏幕,如何才能知道自己手机屏幕质量如何 安卓手机专业测屏神器体验...
  12. 西电计算机好考吗,西电计算机考研难吗 西电计算机专业考研难度有多大?
  13. C# ComboBox:组合框控件
  14. 开放域对话预训练模型总结
  15. localhost不能访问127.0.0.1可以访问的原因及解决方法(整理)
  16. 分布式、集群概念汇总(二)
  17. Win7运行命令的打开方法 Win7运行命令大全(45个)
  18. Kotlin第4篇 【Kotlin】进阶视频课程-关东升-专题视频课程
  19. InfoQ网站作者的文章列表文章详情获取-Java网络爬虫系统性学习与实战系列(13)
  20. 多线程、Redis、rabbitmq面试题

热门文章

  1. 学习记录-class与namespace的区别
  2. 【题解】luogu p1156 垃圾陷阱
  3. tp5 修改配置参数 view_replace_str 无效
  4. sql语句(Oracle和sqlserver)
  5. 2、Keepalived提供日志与双主模型演示
  6. Solr 6.7学习笔记(04)-- Suggest
  7. js事件流、事件代理等
  8. C#调用SQL Server分页存储过程
  9. 让LoadRunner再次走下神坛
  10. IOS零碎技术整理(3)-获取wifi列表