一步步教你写Slack的Loading动画

项目地址:https://github.com/JeasonWong/SlackLoadingView

老规矩,先上效果。

图好大。。

说下第一眼看到这个动画后的思路:

+两根平行线,要用到直线方程 y=kx+b
+另外两根平行线,与之前两根平行线的斜率相乘为-1,即k1*k2=-1
+线条做圆周运动就是k值的不断变化
+然后就是简单的线条长度变化

我相信很多人第一眼会和我有类似的思路,但是当我上了个厕所后意识到我想复杂了~

说下上完厕所后的思路:

不要想着线条是斜的,就是一个普通的线段,一个LineTo搞定(startX和stopX一样,仅Y不同)
线条的垂直更容易,直接Canvas翻转(转过后再转回)
整个动画的圆周运动也是Canvas翻转(转过后不转回)
线条的单度变化依然用属性动画(这是必须的。。)
动画开始前就让整个Canvas旋转
这样一来就太容易了。

我把动画分成了四步:

画布旋转及线条变化动画(Canvas Rotate Line Change)
画布旋转动画(Canvas Rotate)
画布旋转圆圈变化动画(Canvas Rotate Circle Change)
线条变化动画(Line Change)

详细说明前先介绍下成员变量和一些初始化

成员变量

//静止状态 private final int STATUS_STILL = 0; //加载状态 private final int STATUS_LOADING = 1; //线条最大长度 private final int MAX_LINE_LENGTH = dp2px(getContext(), 120); //线条最短长度 private final int MIN_LINE_LENGTH = dp2px(getContext(), 40); //最大间隔时长 private final int MAX_DURATION = 3000; //最小间隔时长 private final int MIN_DURATION = 500; private Paint mPaint; private int[] mColors = new int[]{0xB07ECBDA, 0xB0E6A92C, 0xB0D6014D, 0xB05ABA94}; private int mWidth, mHeight; //动画间隔时长 private int mDuration = MIN_DURATION; //线条总长度 private int mEntireLineLength = MIN_LINE_LENGTH; //圆半径 private int mCircleRadius; //所有动画 private List<Animator> mAnimList = new ArrayList<>(); //Canvas起始旋转角度 private final int CANVAS_ROTATE_ANGLE = 60; //动画当前状态 private int mStatus = STATUS_STILL; //Canvas旋转角度 private int mCanvasAngle; //线条长度 private float mLineLength; //半圆Y轴位置 private float mCircleY; //第几部动画 private int mStep;

初始化

private void initView() { mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setColor(mColors[0]); } private void initData() { mCanvasAngle = CANVAS_ROTATE_ANGLE; mLineLength = mEntireLineLength; mCircleRadius = mEntireLineLength / 5; mPaint.setStrokeWidth(mCircleRadius * 2); mStep = 0; }

一、画布旋转及线条变化动画(Canvas Rotate Line Change)

/** * Animation1 * 动画1 * Canvas Rotate Line Change * 画布旋转及线条变化动画 */ private void startCRLCAnim() { Collection<Animator> animList = new ArrayList<>(); ValueAnimator canvasRotateAnim = ValueAnimator.ofInt(CANVAS_ROTATE_ANGLE + 0, CANVAS_ROTATE_ANGLE + 360); canvasRotateAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mCanvasAngle = (int) animation.getAnimatedValue(); } }); animList.add(canvasRotateAnim); ValueAnimator lineWidthAnim = ValueAnimator.ofFloat(mEntireLineLength, -mEntireLineLength); lineWidthAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mLineLength = (float) animation.getAnimatedValue(); invalidate(); } }); animList.add(lineWidthAnim); AnimatorSet animationSet = new AnimatorSet(); animationSet.setDuration(mDuration); animationSet.playTogether(animList); animationSet.setInterpolator(new LinearInterpolator()); animationSet.addListener(new AnimatorListener() { @Override public void onAnimationEnd(Animator animation) { Log.d("@=>", "动画1结束"); if (mStatus == STATUS_LOADING) { mStep++; startCRAnim(); } } }); animationSet.start(); mAnimList.add(animationSet); }

第一步动画涉及到两个动画同时进行,所以使用了AnimatorSet,这个类很强大,可以让N个动画同时进行(playTogether),也可以让N个动画顺序执行(playSequentially)。

说到这里,其实我的四个动画就是顺序进行的,但是每个动画里又有同时进行的动画,为了讲解方便,我是监听了onAnimationEnd来控制动画执行顺序,其实可以直接使用playSequentially。

上方动画就干了两件事:

1、旋转画布,从CANVAS_ROTATE_ANGLE + 0转到CANVAS_ROTATE_ANGLE + 360,CANVAS_ROTATE_ANGLE是画布初始倾斜角度

2、线条长度变化,从mEntireLineLength到-mEntireLineLength。

对应的onDraw方法:

@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); switch (mStep % 4) { case 0: for (int i = 0; i < mColors.length; i++) { mPaint.setColor(mColors[i]); drawCRLC(canvas, mWidth / 2 - mEntireLineLength / 2.2f, mHeight / 2 - mLineLength, mWidth / 2 - mEntireLineLength / 2.2f, mHeight / 2 + mEntireLineLength, mPaint, mCanvasAngle + i * 90); } break; ... } } ... private void drawCRLC(Canvas canvas, float startX, float startY, float stopX, float stopY, @NonNull Paint paint, int rotate) { canvas.rotate(rotate, mWidth / 2, mHeight / 2); canvas.drawArc(new RectF(startX - mCircleRadius, startY - mCircleRadius, startX + mCircleRadius, startY + mCircleRadius), 180, 180, true, mPaint); canvas.drawLine(startX, startY, stopX, stopY, paint); canvas.drawArc(new RectF(stopX - mCircleRadius, stopY - mCircleRadius, stopX + mCircleRadius, stopY + mCircleRadius), 0, 180, true, mPaint); canvas.rotate(-rotate, mWidth / 2, mHeight / 2); }

是不是很机智,drawCRLC做了三件事:

1、画布旋转后又旋转回来

2、画半圆(为什么要画半圆?不画整个圆?这里留个思考题。)

3、画线条

这样动画1就完成了。

二、画布旋转动画(Canvas Rotate)

/** * Animation2 * 动画2 * Canvas Rotate * 画布旋转动画 */ private void startCRAnim() { ValueAnimator canvasRotateAnim = ValueAnimator.ofInt(mCanvasAngle, mCanvasAngle + 180); canvasRotateAnim.setDuration(mDuration / 2); canvasRotateAnim.setInterpolator(new LinearInterpolator()); canvasRotateAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mCanvasAngle = (int) animation.getAnimatedValue(); invalidate(); } }); canvasRotateAnim.addListener(new AnimatorListener() { @Override public void onAnimationEnd(Animator animation) { Log.d("@=>", "动画2结束"); if (mStatus == STATUS_LOADING) { mStep++; startCRCCAnim(); } } }); canvasRotateAnim.start(); mAnimList.add(canvasRotateAnim); } ... @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); switch (mStep % 4) { ... case 1: for (int i = 0; i < mColors.length; i++) { mPaint.setColor(mColors[i]); drawCR(canvas, mWidth / 2 - mEntireLineLength / 2.2f, mHeight / 2 + mEntireLineLength, mPaint, mCanvasAngle + i * 90); } break; ... } } ... private void drawCR(Canvas canvas, float x, float y, @NonNull Paint paint, int rotate) { canvas.rotate(rotate, mWidth / 2, mHeight / 2); canvas.drawCircle(x, y, mCircleRadius, paint); canvas.rotate(-rotate, mWidth / 2, mHeight / 2); }

有了动画1的底子,那这个就太容易了,只是简单的旋转Canvas。

三、画布旋转圆圈变化动画(Canvas Rotate Circle Change)

/** * Animation3 * 动画3 * Canvas Rotate Circle Change * 画布旋转圆圈变化动画 */ private void startCRCCAnim() { Collection<Animator> animList = new ArrayList<>(); ValueAnimator canvasRotateAnim = ValueAnimator.ofInt(mCanvasAngle, mCanvasAngle + 90, mCanvasAngle + 180); canvasRotateAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mCanvasAngle = (int) animation.getAnimatedValue(); } }); animList.add(canvasRotateAnim); ValueAnimator circleYAnim = ValueAnimator.ofFloat(mEntireLineLength, mEntireLineLength / 4, mEntireLineLength); circleYAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mCircleY = (float) animation.getAnimatedValue(); invalidate(); } }); animList.add(circleYAnim); AnimatorSet animationSet = new AnimatorSet(); animationSet.setDuration(mDuration); animationSet.playTogether(animList); animationSet.setInterpolator(new LinearInterpolator()); animationSet.addListener(new AnimatorListener() { @Override public void onAnimationEnd(Animator animation) { Log.d("@=>", "动画3结束"); if (mStatus == STATUS_LOADING) { mStep++; startLCAnim(); } } }); animationSet.start(); mAnimList.add(animationSet); } ... @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); switch (mStep % 4) { ... case 2: for (int i = 0; i < mColors.length; i++) { mPaint.setColor(mColors[i]); drawCRCC(canvas, mWidth / 2 - mEntireLineLength / 2.2f, mHeight / 2 + mCircleY, mPaint, mCanvasAngle + i * 90); } break; ... } } ... private void drawCRCC(Canvas canvas, float x, float y, @NonNull Paint paint, int rotate) { canvas.rotate(rotate, mWidth / 2, mHeight / 2); canvas.drawCircle(x, y, mCircleRadius, paint); canvas.rotate(-rotate, mWidth / 2, mHeight / 2); }

动画3做了两件事:

1、旋转Canvas

2、变化Circle的Y坐标,达到往里缩的效果

四、线条变化动画(Line Change)

/** * Animation4 * 动画4 * Line Change * 线条变化动画 */ private void startLCAnim() { ValueAnimator lineWidthAnim = ValueAnimator.ofFloat(mEntireLineLength, -mEntireLineLength); lineWidthAnim.setDuration(mDuration); lineWidthAnim.setInterpolator(new LinearInterpolator()); lineWidthAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mLineLength = (float) animation.getAnimatedValue(); invalidate(); } }); lineWidthAnim.addListener(new AnimatorListener() { @Override public void onAnimationEnd(Animator animation) { Log.d("@=>", "动画4结束"); if (mStatus == STATUS_LOADING) { mStep++; startCRLCAnim(); } } }); lineWidthAnim.start(); mAnimList.add(lineWidthAnim); } ... @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); switch (mStep % 4) { ... case 3: for (int i = 0; i < mColors.length; i++) { mPaint.setColor(mColors[i]); drawLC(canvas, mWidth / 2 - mEntireLineLength / 2.2f, mHeight / 2 + mEntireLineLength, mWidth / 2 - mEntireLineLength / 2.2f, mHeight / 2 + mLineLength, mPaint, mCanvasAngle + i * 90); } break; } } ... private void drawLC(Canvas canvas, float startX, float startY, float stopX, float stopY, @NonNull Paint paint, int rotate) { canvas.rotate(rotate, mWidth / 2, mHeight / 2); canvas.drawArc(new RectF(startX - mCircleRadius, startY - mCircleRadius, startX + mCircleRadius, startY + mCircleRadius), 0, 180, true, mPaint); canvas.drawLine(startX, startY, stopX, stopY, paint); canvas.drawArc(new RectF(stopX - mCircleRadius, stopY - mCircleRadius, stopX + mCircleRadius, stopY + mCircleRadius), 180, 180, true, mPaint); canvas.rotate(-rotate, mWidth / 2, mHeight / 2); }

动画4只做了线条的变化。

这样整个Slack的Loading动画就完成了,是不是很简单。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

时间: 2024-10-27 17:34:16

一步步教你写Slack的Loading动画的相关文章

一步步教你写Slack的Loading动画_Android

项目地址:https://github.com/JeasonWong/SlackLoadingView 老规矩,先上效果. 图好大.. 说下第一眼看到这个动画后的思路: +两根平行线,要用到直线方程 y=kx+b +另外两根平行线,与之前两根平行线的斜率相乘为-1,即k1*k2=-1 +线条做圆周运动就是k值的不断变化 +然后就是简单的线条长度变化 我相信很多人第一眼会和我有类似的思路,但是当我上了个厕所后意识到我想复杂了~ 说下上完厕所后的思路: 不要想着线条是斜的,就是一个普通的线段,一个L

iOS动画教你编写Slack的Loading动画进阶篇_IOS

前几天看了一篇关于动画的博客叫手摸手教你写 Slack 的 Loading 动画,看着挺炫,但是是安卓版的,寻思的着仿造着写一篇iOS版的,下面是我写这个动画的分解~  老规矩先上图和demo地址: 刚看到这个动画的时候,脑海里出现了两个方案,一种是通过drawRect画出来,然后配合CADisplayLink不停的绘制线的样式:第二种是通过CAShapeLayer配合CAAnimation来实现动画效果.再三考虑觉得使用后者,因为前者需要计算很多,比较复杂,而且经过测试前者相比于后者消耗更多的

只有20行Javascript代码!手把手教你写一个页面模板引擎

AbsurdJS 作者写的一篇教程,一步步教你怎样用 Javascript 实现一个纯客户端的模板引擎.整个引擎实现只有不到 20 行代码.如果你能从头看到尾的话,还能有不少收获的.你甚至可以跟随大牛的脚步也自己动手写一个引擎.以下是全文. 不知道你有木有听说过一个基于Javascript的Web页面预处理器,叫做AbsurdJS.我是它的作者,目前我还在不断地完善它.最初我只是打算写一个CSS的预处理器,不过后来扩展到了CSS和HTML,可以用来把Javascript代码转成CSS和HTML代

一步步教你配置SQL SERVER合并复制(八)代码部分

一步步教你配置SQL SERVER合并复制(八)代码部分(使用.NET CompactFramework) 这个系列的翻译也拖了一段时间,现在决定一次性将它理清了.关于合并复制服务器的配置在前面的翻译文章中都已经详细地论述完了,现在终于到了订阅者是如何使用我们配制好的合并复制了.下面的代码是针对.NET CompactFramework的,其实整个合并复制的过程中,安装和配置占了95%的时间,而创建订阅仅仅需要5%的时间,合并复制的这个特征减小了我们敲代码时出现错误的几率,将更多地工作移植到了服

教机器写代码:增强拓扑进化网络(NEAT)

教机器写代码:增强拓扑进化网络(NEAT) 在这篇文章中,我将向大家介绍一种名为增强拓扑进化网络(NEAT)的机器学习方法. 介绍 我喜欢学习.每当遇到从未接触过的书籍或论文,并开始阅读的时候,我都会感到非常兴奋.有些人喜欢在空闲时间玩填字游戏.数独或者猜谜语,以刺激自己的意识.而我喜欢阅读论文并尝试实现文中所提到的算法.而且,由于喜欢学习,我一直对自己无法学到所有想要学的东西而感到难过.我特别感兴趣的是学习如何让电脑为我而学习,让电脑为我提供一些有趣的信息,从而最大限度地提高学习的效率和有效性

手把手教你写回调函数

                                        手把手教你写回调函数       手把手教你写回调函数源代码   回调函数的主要功能就是提供统一的接口以及事件的通知.如果你是从事middleware,框架,提供API编程的,那么你肯定少不了要使用回调函数. 所谓提供统一接口或者事件的通知即:当发生某一事件或者出现某一个状态之后必定会进行某种操作.但是这个操作又不能写死,不同的环境不同的程序会有不同的实现,也就是说提供接口的人不想把逻辑写死,而是由调用接口的人来实现

比较详细的手把手教你写批处理(willsort题注版)第1/5页_DOS/BAT

另,建议Climbing兄取文不用拘泥于国内,此类技术文章,内外水平相差极大:与其修正国内只言片语,不如翻译国外优秀著述. -------------------------------------------------------- 标题:手把手教你写批处理-批处理的介绍 作者:佚名 编者:Climbing 题注:willsort 日期:2004-09-21 -------------------------------------------------------- 批处理的介绍 扩展名

手把手教你写Kconfig---基于tiny4412开发板(增强版)

Kconfig怎么写的在上节就已经教大家写了. 这节我们来写写增强版的,因为Kconfig有太多太多可以配置的,所以这里我就不给出图片演示了,请参考上节的文章,再来看这节大家就会大彻大悟,然后自己去尝试吧. 基本上最常见的配置就是以下的这些. 文章链接如下: http://blog.csdn.net/morixinguan/article/details/54744237 在make menuconfig添加Kconfig配置简单的选项有很多的Kconfig组成./scripts/Kconfig

Redis从单机到集群,一步步教你环境部署以及使用

Redis作为缓存系统来说还是很有价值的,在大数据方向里,也是需要有缓存系统的.一般可以考虑tachyon或者redis,由于redis安装以及使用更简单,所以还是优先考虑了它.那么在一些场景下为了保证数据的可靠性,就需要采用集群的模式部署,因此本篇文章就基于Redis Cluster的背景讲解下部署以及后期的使用. 大致会包括下面的内容: Redis单机版的安装以及验证 Redis集群版的安装以及验证 使用图形化工具访问Redis 使用Jedis访问Redis 使用JedisCluster访问