一步步教你写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动画就完成了,是不是很简单。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。