Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 一步步教你寫Slack的Loading動畫

一步步教你寫Slack的Loading動畫

編輯:關於Android編程

項目地址: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動畫就完成了,是不是很簡單。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持本站。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved