欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

一步步教你写Slack的Loading动画

程序员文章站 2024-03-06 19:28:38
项目地址:https://github.com/jeasonwong/slackloadingview 老规矩,先上效果。 图好大。。 说下第一眼看到这个动画后的...

项目地址:https://github.com/jeasonwong/slackloadingview

老规矩,先上效果。

一步步教你写Slack的Loading动画

图好大。。

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

+两根平行线,要用到直线方程 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动画就完成了,是不是很简单。

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