一步步教你寫Slack的Loading動(dòng)畫
項(xiàng)目地址:https://github.com/JeasonWong/SlackLoadingView
老規(guī)矩,先上效果。
圖好大。。
說(shuō)下第一眼看到這個(gè)動(dòng)畫后的思路:
+兩根平行線,要用到直線方程 y=kx+b
+另外兩根平行線,與之前兩根平行線的斜率相乘為-1,即k1*k2=-1
+線條做圓周運(yùn)動(dòng)就是k值的不斷變化
+然后就是簡(jiǎn)單的線條長(zhǎng)度變化
我相信很多人第一眼會(huì)和我有類似的思路,但是當(dāng)我上了個(gè)廁所后意識(shí)到我想復(fù)雜了~
說(shuō)下上完廁所后的思路:
不要想著線條是斜的,就是一個(gè)普通的線段,一個(gè)LineTo搞定(startX和stopX一樣,僅Y不同)
線條的垂直更容易,直接Canvas翻轉(zhuǎn)(轉(zhuǎn)過(guò)后再轉(zhuǎn)回)
整個(gè)動(dòng)畫的圓周運(yùn)動(dòng)也是Canvas翻轉(zhuǎn)(轉(zhuǎn)過(guò)后不轉(zhuǎn)回)
線條的單度變化依然用屬性動(dòng)畫(這是必須的。。)
動(dòng)畫開始前就讓整個(gè)Canvas旋轉(zhuǎn)
這樣一來(lái)就太容易了。
我把動(dòng)畫分成了四步:
畫布旋轉(zhuǎn)及線條變化動(dòng)畫(Canvas Rotate Line Change)
畫布旋轉(zhuǎn)動(dòng)畫(Canvas Rotate)
畫布旋轉(zhuǎn)圓圈變化動(dòng)畫(Canvas Rotate Circle Change)
線條變化動(dòng)畫(Line Change)
詳細(xì)說(shuō)明前先介紹下成員變量和一些初始化
成員變量
//靜止?fàn)顟B(tài) private final int STATUS_STILL = 0; //加載狀態(tài) private final int STATUS_LOADING = 1; //線條最大長(zhǎng)度 private final int MAX_LINE_LENGTH = dp2px(getContext(), 120); //線條最短長(zhǎng)度 private final int MIN_LINE_LENGTH = dp2px(getContext(), 40); //最大間隔時(shí)長(zhǎng) private final int MAX_DURATION = 3000; //最小間隔時(shí)長(zhǎng) private final int MIN_DURATION = 500; private Paint mPaint; private int[] mColors = new int[]{0xB07ECBDA, 0xB0E6A92C, 0xB0D6014D, 0xB05ABA94}; private int mWidth, mHeight; //動(dòng)畫間隔時(shí)長(zhǎng) private int mDuration = MIN_DURATION; //線條總長(zhǎng)度 private int mEntireLineLength = MIN_LINE_LENGTH; //圓半徑 private int mCircleRadius; //所有動(dòng)畫 private List<Animator> mAnimList = new ArrayList<>(); //Canvas起始旋轉(zhuǎn)角度 private final int CANVAS_ROTATE_ANGLE = 60; //動(dòng)畫當(dāng)前狀態(tài) private int mStatus = STATUS_STILL; //Canvas旋轉(zhuǎn)角度 private int mCanvasAngle; //線條長(zhǎng)度 private float mLineLength; //半圓Y軸位置 private float mCircleY; //第幾部動(dòng)畫 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; }
一、畫布旋轉(zhuǎn)及線條變化動(dòng)畫(Canvas Rotate Line Change)
/** * Animation1 * 動(dòng)畫1 * Canvas Rotate Line Change * 畫布旋轉(zhuǎn)及線條變化動(dòng)畫 */ 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("@=>", "動(dòng)畫1結(jié)束"); if (mStatus == STATUS_LOADING) { mStep++; startCRAnim(); } } }); animationSet.start(); mAnimList.add(animationSet); }
第一步動(dòng)畫涉及到兩個(gè)動(dòng)畫同時(shí)進(jìn)行,所以使用了AnimatorSet,這個(gè)類很強(qiáng)大,可以讓N個(gè)動(dòng)畫同時(shí)進(jìn)行(playTogether),也可以讓N個(gè)動(dòng)畫順序執(zhí)行(playSequentially)。
說(shuō)到這里,其實(shí)我的四個(gè)動(dòng)畫就是順序進(jìn)行的,但是每個(gè)動(dòng)畫里又有同時(shí)進(jìn)行的動(dòng)畫,為了講解方便,我是監(jiān)聽了onAnimationEnd來(lái)控制動(dòng)畫執(zhí)行順序,其實(shí)可以直接使用playSequentially。
上方動(dòng)畫就干了兩件事:
1、旋轉(zhuǎn)畫布,從CANVAS_ROTATE_ANGLE + 0轉(zhuǎn)到CANVAS_ROTATE_ANGLE + 360,CANVAS_ROTATE_ANGLE是畫布初始傾斜角度
2、線條長(zhǎng)度變化,從mEntireLineLength到-mEntireLineLength。
對(duì)應(yīng)的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); }
是不是很機(jī)智,drawCRLC做了三件事:
1、畫布旋轉(zhuǎn)后又旋轉(zhuǎn)回來(lái)
2、畫半圓(為什么要畫半圓?不畫整個(gè)圓?這里留個(gè)思考題。)
3、畫線條
這樣動(dòng)畫1就完成了。
二、畫布旋轉(zhuǎn)動(dòng)畫(Canvas Rotate)
/** * Animation2 * 動(dòng)畫2 * Canvas Rotate * 畫布旋轉(zhuǎn)動(dòng)畫 */ 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("@=>", "動(dòng)畫2結(jié)束"); 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); }
有了動(dòng)畫1的底子,那這個(gè)就太容易了,只是簡(jiǎn)單的旋轉(zhuǎn)Canvas。
三、畫布旋轉(zhuǎn)圓圈變化動(dòng)畫(Canvas Rotate Circle Change)
/** * Animation3 * 動(dòng)畫3 * Canvas Rotate Circle Change * 畫布旋轉(zhuǎn)圓圈變化動(dòng)畫 */ 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("@=>", "動(dòng)畫3結(jié)束"); 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); }
動(dòng)畫3做了兩件事:
1、旋轉(zhuǎn)Canvas
2、變化Circle的Y坐標(biāo),達(dá)到往里縮的效果
四、線條變化動(dòng)畫(Line Change)
/** * Animation4 * 動(dòng)畫4 * Line Change * 線條變化動(dòng)畫 */ 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("@=>", "動(dòng)畫4結(jié)束"); 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); }
動(dòng)畫4只做了線條的變化。
這樣整個(gè)Slack的Loading動(dòng)畫就完成了,是不是很簡(jiǎn)單。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- iOS動(dòng)畫教你編寫Slack的Loading動(dòng)畫進(jìn)階篇
- Android自定義加載loading view動(dòng)畫組件
- Android實(shí)現(xiàn)創(chuàng)意LoadingView動(dòng)畫效果
- 一看就喜歡的loading動(dòng)畫效果Android分析實(shí)現(xiàn)
- jQuery實(shí)現(xiàn)彩帶延伸效果的網(wǎng)頁(yè)加載條loading動(dòng)畫
- 三款A(yù)ndroid炫酷Loading動(dòng)畫組件推薦
- Winform圓形環(huán)繞的Loading動(dòng)畫實(shí)現(xiàn)代碼
- javascript制作loading動(dòng)畫效果 loading效果
- javascript 通用loading動(dòng)畫效果實(shí)例代碼
- loading動(dòng)畫特效小結(jié)
相關(guān)文章
Android多國(guó)語(yǔ)言轉(zhuǎn)換Excel及Excel轉(zhuǎn)換為string詳解
這篇文章主要給大家介紹了關(guān)于Android多國(guó)語(yǔ)言轉(zhuǎn)換Excel以及Excel轉(zhuǎn)換為string的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起看看吧2019-01-01Android中簡(jiǎn)單調(diào)用圖片、視頻、音頻、錄音和拍照的方法
這篇文章主要介紹了Android中簡(jiǎn)單調(diào)用圖片、視頻、音頻、錄音和拍照的方法,涉及Android多媒體操作的常用技巧,需要的朋友可以參考下2016-08-08Android源碼中final關(guān)鍵字的用法及final,finally,finalize的區(qū)別
Android的源碼中很多地方對(duì)final關(guān)鍵字的用法很是“別出心裁”,之所以這么說(shuō)是因?yàn)槲覐臎](méi)看過(guò)是這么使用final關(guān)鍵字的,通過(guò)本文給大家分享Android源碼中final關(guān)鍵字的用法及final,finally,finalize的區(qū)別,感興趣的朋友一起學(xué)習(xí)吧2015-12-12修改Android FloatingActionButton的title的文字顏色及背景顏色實(shí)例詳解
這篇文章主要介紹了修改Android FloatingActionButton的title的文字顏色及背景顏色實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-03-03Android編程開發(fā)實(shí)現(xiàn)帶進(jìn)度條和百分比的多線程下載
這篇文章主要介紹了Android編程開發(fā)實(shí)現(xiàn)帶進(jìn)度條和百分比的多線程下載,總結(jié)了前面關(guān)于Java多線程下載的技巧,實(shí)例分析了Android實(shí)現(xiàn)帶百分比和進(jìn)度條的多線程下載技巧,需要的朋友可以參考下2015-12-12Android RichText 讓Textview輕松的支持富文本(圖像ImageSpan、點(diǎn)擊效果等等類似QQ微信聊
AndroidRichText幫助實(shí)現(xiàn)像QQ,微信一樣的,一個(gè)TextView里既有文字又有表情又有圖片的效果,采用插件化的框架,代碼簡(jiǎn)單,可拓展性強(qiáng)2016-01-01利用Warensoft Stock Service編寫高頻交易軟件
本文主要介紹了利用Warensoft Stock Service編寫高頻交易軟件的方法步驟,具有一定的參考價(jià)值,下面跟著小編一起來(lái)看下吧2017-01-01Kotlin協(xié)程開發(fā)之Flow的融合與Channel容量及溢出策略介紹
這篇文章主要介紹了Kotlin協(xié)程:Flow的融合、Channel容量、溢出策略,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-09-09Android 控制車載藍(lán)牙播放音樂(lè)詳解流程
本篇文章介紹了手機(jī)端音樂(lè)暫停和播放狀態(tài),從服務(wù)端告訴客戶端、設(shè)備端實(shí)現(xiàn)暫停、播放、上一首、下一首等功能的實(shí)現(xiàn),通讀本篇對(duì)大家的學(xué)習(xí)或工作具有一定的價(jià)值,需要的朋友可以參考下2021-10-10