編輯:關於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動畫就完成了,是不是很簡單。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持本站。
一.效果展示在Android手機的桌面上,我們經常可以看到如下小控件在這些控件上,可以顯示我們APP的一些重要的交互信息,以我最近開發的手機衛士為例,在widget上可以
序言自從谷歌在2014年的IO大會上推出了Material Design新的設計規范後,安卓應用的整體美觀程度提升了很大的一個層次, 安卓再也不是又黑又丑的界面,取而代之
本文實例講述了Android通過json向MySQL中寫入數據的方法。分享給大家供大家參考,具體如下:先說一下如何通過json將Android程序中的數據上傳到MySQL
有時候因公司需求,要求合並兩個APP 使用裡面的功能。 平台:Studio 小白鼠:二維碼掃描 和自己項目 具體步驟: /** * 1.將解壓後的android