一步步教你写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动画就完成了,是不是很简单。

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

(0)

相关推荐

  • javascript 通用loading动画效果实例代码

    由于项目中多处要给ajax提交的时候增加等待动画效果,所以就写了一个简单的通用js方法:代码如下: 复制代码 代码如下: /*ajax提交的延时等待效果*/ var AjaxLoding = new Object(); //wraperid : 显示loding图片的容器元素//ms:表示loding图标显示的时长,毫秒//envent:表示出发事件的事件源对象,用于获得出发事件的对象//callback:表示动画结束后执行的回掉方法//stop()方法表示在回掉方法执行成功后执行的隐藏动画的操

  • 一看就喜欢的loading动画效果Android分析实现

    还是比较有新意,复杂度也不是非常高,所以就花时间整理一下,我们先一起看下原gif图效果: 从效果上看,我们需要考虑以下几个问题: 1.叶子的随机产生: 2.叶子随着一条正余弦曲线移动: 3.叶子在移动的时候旋转,旋转方向随机,正时针或逆时针: 4.叶子遇到进度条,似乎是融合进入: 5.叶子不能超出最左边的弧角: 7.叶子飘出时的角度不是一致,走的曲线的振幅也有差别,否则太有规律性,缺乏美感: 总的看起来,需要注意和麻烦的地方主要是以上几点,当然还有一些细节问题,比如最左边是圆弧等等: 那接下来我

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

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

  • jQuery实现彩带延伸效果的网页加载条loading动画

    本文实例讲述了jQuery实现彩带延伸效果的网页加载条loading动画.分享给大家供大家参考,具体如下: 这里介绍的jQuery彩带效果网页加载条动画,我觉得挺有创意的,虽然难度不算大,但能想到用这样一个背景来做Loading加载条,也实属不易,不服气的,你为什么就没有想到这样做呢?本网页加载条效果使用了jQuery插件. 运行效果截图如下: 在线演示地址如下: http://demo.jb51.net/js/2015/jquery-n-color-cha-web-loading-demo/

  • 三款Android炫酷Loading动画组件推荐

    最近突然心血来潮,对一些Loading感兴趣,Loading这玩意说重要也重要,说不重要也不重要,因为这是一个提升你产品体验的一个细节,如果loading做的好,对于一些耗时需要用户等待的页面来说会转移用户注意力,不会显得那么烦躁,所以你可以看到市面上各种各样好玩的Loading动画,那么这篇博客就准备收集下一些Loading动画吧,从这些实现思路上可以打开你们自己的思维,没准也会有创新好玩的Loading动画出现. 暂且先列举些最近GitHub新鲜出炉的Loading CircleProgre

  • javascript制作loading动画效果 loading效果

    复制代码 代码如下: /*ajax提交的延时等待效果*/ var AjaxLoding = new Object(); //wraperid : 显示loding图片的容器元素//ms:表示loding图标显示的时长,毫秒//envent:表示出发事件的事件源对象,用于获得出发事件的对象//callback:表示动画结束后执行的回掉方法//stop()方法表示在回掉方法执行成功后执行的隐藏动画的操作AjaxLoding.load = function(lodingid,ms,event,left

  • loading动画特效小结

    话不多说,请看代码 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>lodading动画效果上</title> <link rel="stylesheet" href="style.css"/> <style> .box{ width: 10

  • Android实现创意LoadingView动画效果

    Android上的热火锅煮萝卜蔬菜的Loading动画效果. 这是一个锅煮萝卜的Loading动画,效果仿照自之前IOS上看到的一个效果,觉得挺有意思,就移植过来了,在此完成了Dialog的样式,方便使用者作为LoadingView去使用. 关键性代码: package yellow5a5.demo.boilingloadingview.View; import android.animation.Animator; import android.animation.AnimatorListen

  • Winform圆形环绕的Loading动画实现代码

    之前写了一个WPF的圆形环绕的Loading动画,现在写一个Winform的圆形环绕的Loading动画. 1.新建Winform项目,添加一个pictureBox控件,命名为:pictureBox: 2.引用中添加using System.Drawing.Drawing2D; 3.Form窗体命名为:Loading,cs全部代码如下: 复制代码 代码如下: using System;using System.Collections.Generic;using System.ComponentM

  • Android自定义加载loading view动画组件

    在github上找的一个有点酷炫的loading动画https://github.com/Fichardu/CircleProgress 我写写使用步骤 自定义view(CircleProgress )的代码 package com.hysmarthotel.view; import com.hysmarthotel.roomcontrol.R; import com.hysmarthotel.util.EaseInOutCubicInterpolator; import android.ani

随机推荐