首页 > 系统 > Android > 正文

一步步教你写Slack的Loading动画

2019-12-12 05:19:39
字体:
来源:转载
供稿:网友

项目地址: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动画就完成了,是不是很简单。

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

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表