最近比較忙,煩心的事情也不少弓候。就迷上了一款游戲《守望先鋒》,差點(diǎn)就沒回來他匪。
言歸正傳菇存,前些日子看到一個(gè)很炫酷的loadingView打掘,看到的時(shí)候感覺迈套,這個(gè)感覺怎么說呢,用英語說就是amazing(太TM吊了)兆览。
我也僅僅只是通過別人的博客悼沈,加上一點(diǎn)自己的理解寫的這篇博客贱迟,目的是想要和大家分享,順便記錄一下絮供。感覺實(shí)現(xiàn)一個(gè)這么炫酷的動(dòng)畫還是感覺挺有成就感動(dòng)的(畢竟菜鳥一枚)衣吠。這里先放上原博主的鏈接,感謝這位大神壤靶。這邊博客把實(shí)現(xiàn)過程已經(jīng)寫的很清晰的建議有些自定義view基礎(chǔ)的人缚俏,先去看這里先放上原博主的博客,然后自己實(shí)現(xiàn)以下贮乳,我這里會(huì)對(duì)整個(gè)view的實(shí)現(xiàn)過程詳細(xì)的講一下忧换。
此次時(shí)間有點(diǎn)倉(cāng)促,沒有對(duì)代碼進(jìn)行優(yōu)化向拆,同時(shí)也有部分原作者的代碼亚茬。希望大家諒解,主要是給大家提供一個(gè)思路浓恳。
先看一下效果圖:
怎么樣刹缝,我沒說錯(cuò)吧碗暗,第一眼看見就眼前一亮。下面我們就對(duì)整個(gè)過程進(jìn)行詳細(xì)的講解赞草。
拆分動(dòng)畫
- 和葉子一樣顏色的進(jìn)度條
- 右側(cè)旋轉(zhuǎn)的白色電風(fēng)扇
- 漂浮的葉子(原博主說的很細(xì)致我這里直接引用原話)
1.葉子的隨機(jī)產(chǎn)生讹堤;
2.葉子隨著一條正余弦曲線移動(dòng);
3.葉子在移動(dòng)的時(shí)候旋轉(zhuǎn)厨疙,旋轉(zhuǎn)方向隨機(jī)洲守,正時(shí)針或逆時(shí)針;
4.葉子遇到進(jìn)度條沾凄,似乎是融合進(jìn)入梗醇;
5.葉子不能超出最左邊的弧角;
7.葉子飄出時(shí)的角度不是一致撒蟀,走的曲線的振幅也有差別叙谨,否則太有規(guī)律性,缺乏美感保屯; - 最后又一個(gè)結(jié)束動(dòng)畫手负,風(fēng)扇消失,然后“100%”出現(xiàn)
整個(gè)動(dòng)畫就是這樣子的姑尺,難點(diǎn)就是繪制葉子要滿足以上的7點(diǎn)竟终。
定義屬性
private static final int DEFAULT_BG_OUTER = 0xfffde399; // 外部邊框的背景顏色
private static final String DEFAULT_WHITE = "#fffefd";
private static final int DEFAULT_BG_INNER = 0xffffa800; //內(nèi)部進(jìn)度條的顏色
private static final String DEFAULT_BG_FAN = "#fcce5b"; // 風(fēng)扇 扇葉的顏色
private static final int DEFAULT_WIDTH = 300;
private static final int DEFAULT_HEIGHT = 600;
//振幅的強(qiáng)度
private static final int LOW_AMPLITUDE = 0;
private static final int NORMAL_AMPLITUDE = 1;
private static final int HIGH_AMPLITUDE = 2;
private static final int DEFAULT_AMPLITUDE = 20;
// 葉子飄動(dòng)一個(gè)周期所花的時(shí)間
private static final int LEAF_FLY_TIME = 2000;
private static final int LEAF_ROTATE_TIME = 2000;
private Resources mResources;
// 定義畫筆
private Paint innerPaint;
private Paint outerPaint;
private Paint fanPaint;
private Paint fanBgPaint;
private Paint textPaint;
// view的大小 和 “100%”的高度
private int mWidth;
private int mHeight;
private float textHeight;
//外部圓半徑 內(nèi)部圓半徑 風(fēng)扇背景的半徑
private float outerRadius;
private float innerRadius;
private float fanBgRadius;
//各種路徑
private RectF outerCircle;
private RectF outerRectangle;
private RectF innerCircle;
private RectF innerRectangle;
private RectF fanWhiteRect;
//電風(fēng)扇 扇葉路徑
private Path mPath;
private Path nPath;
// 定義結(jié)束的屬性動(dòng)畫
private ValueAnimator progressAnimator;
private ValueAnimator completedAnimator;
//進(jìn)度值
private float maxProgress = 100;
private float currentProgress;
private float completedProgress;
//計(jì)算時(shí)間增量和progress增量
private long preTime ;
private long addTime;
private float addProgress;
private float preProgress;
//先填充半圓的進(jìn)度 和 長(zhǎng)方形的時(shí)間
private float firstStepTime;
private float secondStepTime;
//和葉片相關(guān)
private Bitmap mLeafBitmap;
private int mLeafWidth;
private int mLeafHeight;
private int mLeafFlyTime = LEAF_FLY_TIME;
private int mLeafRotateTime = LEAF_ROTATE_TIME;
private int mAddTime;
private float mAmplitudeDisparity = DEFAULT_AMPLITUDE;
//判斷是否加載完畢 然后執(zhí)行結(jié)束動(dòng)畫
private boolean isFinished;
//精度條的總長(zhǎng)度
private float mProgressWidth;
private List<Leaf> leafInfos;
//對(duì) 外面的邊框緩存
private WeakReference<Bitmap> outBorderBitmapCache;
這里定義的屬性比較多,但是還是都通熟易懂的切蟋。
OnDraw()
我們這先看一下onDraw方法吧统捶,整個(gè)的繪制流程是都放生在這個(gè)方法里面。我們先梳理一下繪制的流程柄粹,具體畫每個(gè)圖形后面我會(huì)詳細(xì)講解喘鸟。
protected void onDraw(Canvas canvas) {
//判斷背景有沒有緩存(這里的背景是指,黃色進(jìn)度條外面的邊框)
Bitmap outBorderBitmap = outBorderBitmapCache == null ? null : outBorderBitmapCache.get();
if (outBorderBitmap == null || outBorderBitmap.isRecycled()) {
outBorderBitmap = getBitmap();
outBorderBitmapCache = new WeakReference<Bitmap>(outBorderBitmap);
}
//對(duì)畫布保存主要是要用Xfermode對(duì)圖像處理驻右,主要是不想讓葉子飛出邊界
//如果不了解Xfermode的同學(xué)建議先去看一下什黑,很有用的一個(gè)東西
int sc = canvas.saveLayer(0, 0, mWidth, mHeight, null, Canvas.MATRIX_SAVE_FLAG |
Canvas.CLIP_SAVE_FLAG |
Canvas.HAS_ALPHA_LAYER_SAVE_FLAG |
Canvas.FULL_COLOR_LAYER_SAVE_FLAG |
Canvas.CLIP_TO_LAYER_SAVE_FLAG);
canvas.drawBitmap(outBorderBitmap, 0, 0, outerPaint);
// canvas.translate(mWidth / 10, mHeight / 2);
//畫葉子
drawLeaf(canvas);
//恢復(fù)畫布
canvas.restoreToCount(sc);
canvas.translate(mWidth / 10, mHeight / 2);
//畫內(nèi)部圓
drawInnerCircle(canvas);
//畫風(fēng)扇白色的背景
canvas.drawArc(fanWhiteRect, 90, 360, true, fanPaint);
//畫風(fēng)扇的黃色背景
canvas.save();
canvas.scale(0.9f, 0.9f, 8 * outerRadius, 0);
canvas.drawArc(fanWhiteRect, 90, 360, true, fanBgPaint);
canvas.restore();
//畫扇葉
canvas.save();
drawFan(canvas, true);
canvas.restore();
//結(jié)束動(dòng)畫
//結(jié)束動(dòng)畫是指 電風(fēng)扇的扇葉從扇葉變成100%字樣
if (isFinished) {
showCompletedText(canvas);
} else {
//這里重新繪制 主要是為了畫葉子
invalidate();
}
}
首先我們先說一下畫背景(這里的背景指的是進(jìn)度條外面的邊框)
先看一下具體實(shí)現(xiàn)
public Bitmap getBitmap() {
//這里先產(chǎn)生一個(gè)一個(gè)畫布,畫布的大小就是view的大小
Bitmap bitmap = Bitmap.createBitmap(mWidth, mHeight,Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.translate(mWidth / 10, mHeight / 2);
canvas.drawArc(outerCircle, 90, 180, true, outerPaint);
canvas.drawRect(outerRectangle, outerPaint);
return bitmap;
}
這里我們讓背景作為bitmap返回主要是 要使用Xfermode方法旺入。先移動(dòng)坐標(biāo)系到我們想要的位置兑凿,然后背景是由一個(gè)左半圓和一個(gè)矩形組成。至于為什么要使用Xfermode茵瘾,這里先說明一下礼华,我們期望葉子是不可以飄出背景外的。(就是說葉子飄出背景外的地方要變成透明的)拗秘。
畫進(jìn)度條
//先填充半圓
private void drawInnerCircle(Canvas canvas) {
firstStepProgress = innerRadius / (innerRadius + 7 * outerRadius);
if (currentProgress > firstStepProgress) {
canvas.drawArc(innerCircle, 90, 180, true, innerPaint);
drawInnerRectangle(canvas);
} else {
//這里就是繪制半圓的執(zhí)行(方法是繪制圓皇バ酢)
canvas.drawArc(innerCircle, 180 - 90 * currentProgress / firstStepTime, 180 * currentProgress / firstStepTime, false, innerPaint);
}
}
//填充剩下的長(zhǎng)方形
private void drawInnerRectangle(Canvas canvas) {
secondStepProgress = 1 - firstStepProgress;
//判斷是否結(jié)束,結(jié)束了會(huì)執(zhí)行結(jié)束動(dòng)畫
if (currentProgress >= 1) {
if (!isFinished) {
isFinished = true;
completedAnimator.start();
}
} else {
canvas.drawRect(-1, -innerRadius, 7 * outerRadius * (currentProgress - firstStepProgress) / secondStepProgress, innerRadius, innerPaint);
}
}
進(jìn)度條和背景是一樣的雕旨,都是先都前半圓和一個(gè)矩形組成的扮匠。先計(jì)算半圓所占進(jìn)度捧请,當(dāng)currentProgress沒有超過firstStepProgress時(shí)候,先繪制半圓部分棒搜,之后繪制矩形疹蛉。
繪制風(fēng)扇
參數(shù)分別是 canvas 畫布,isNeedRotate 風(fēng)扇是否旋轉(zhuǎn)力麸。
//畫扇葉
private void drawFan(Canvas canvas, boolean isNeedRotate) {
canvas.save();
//加載的時(shí)候旋轉(zhuǎn)風(fēng)扇可款,負(fù)數(shù)是逆時(shí)針旋轉(zhuǎn),默認(rèn)旋轉(zhuǎn)5圈
if (isNeedRotate) {
canvas.rotate(-currentProgress * 360 * 5, 8 * outerRadius, 0);
}
//結(jié)束動(dòng)畫時(shí)候需要不斷的縮小風(fēng)扇克蚂,然后“100%”從小變大
if (completedProgress != 0) {
canvas.scale(1 - completedProgress, 1 - completedProgress, 8 * outerRadius, 0);
}
//旋轉(zhuǎn)畫扇葉闺鲸,扇葉使用path繪制的
for (float i = 0; i <= 270; i = i + 90) {
canvas.rotate(i, 8 * outerRadius, 0);
canvas.drawPath(mPath, fanPaint);
}
//這個(gè)是風(fēng)扇中間的小點(diǎn)
canvas.drawCircle(8 * outerRadius, 0, 5 * (1 - completedProgress), fanPaint);
canvas.restore();
}
繪制結(jié)束動(dòng)畫
結(jié)束動(dòng)畫 這里我們用的是屬性動(dòng)畫提供的0-1的值實(shí)現(xiàn)的。這個(gè)過程主要是把進(jìn)度條補(bǔ)齊以及風(fēng)扇消失埃叭,然后“100%”字樣顯示摸恍。
//結(jié)束時(shí)動(dòng)畫 展示“100%”字樣
private void showCompletedText(Canvas canvas) {
//補(bǔ)齊進(jìn)度條
canvas.drawRect(-1, -innerRadius, (7 + completedProgress) * outerRadius, innerRadius, innerPaint);
canvas.drawArc(fanWhiteRect, 90, 360, true, fanPaint);
//繪制風(fēng)扇的背景
canvas.save();
canvas.scale(0.9f, 0.9f, 8 * outerRadius, 0);
canvas.drawArc(fanWhiteRect, 90, 360, true, fanBgPaint);
canvas.restore();
if (completedProgress == 1) {
textPaint.setTextSize(60);
canvas.drawText("100%", 8 * outerRadius, textHeight, textPaint);
} else {
drawFan(canvas, completedProgress, false);
textPaint.setTextSize(60 * completedProgress);
canvas.drawText("100%", 8 * outerRadius, textHeight, textPaint);
}
}
繪制葉子
因?yàn)槿~子是一直在飄蕩的,這里利用系統(tǒng)的時(shí)間赤屋,來計(jì)算葉子的坐標(biāo)立镶。
private class Leaf {
// 在繪制部分的位置
float x, y;
// 控制葉子飄動(dòng)的幅度
int type;
// 旋轉(zhuǎn)角度
int rotateAngle;
// 旋轉(zhuǎn)方向--0代表順時(shí)針,1代表逆時(shí)針
int rotateDirection;
// 起始時(shí)間(ms)
long startTime;
}
/**
* 畫葉子
*/
private void drawLeaf(Canvas canvas) {
long currentTime = System.currentTimeMillis();
canvas.save();
//這里進(jìn)行了 一次畫布平移
canvas.translate(mWidth / 10 - innerRadius, mHeight / 2 - outerRadius);
for (Leaf leaf : leafInfos) {
//如果系統(tǒng)當(dāng)前的時(shí)間大于葉子開始繪制的時(shí)間类早,就去獲取葉子的坐標(biāo)
if (currentTime > leaf.startTime && leaf.startTime != 0) {
getLocation(leaf, currentTime);
// 通過時(shí)間關(guān)聯(lián)旋轉(zhuǎn)角度谜慌,則可以直接通過修改LEAF_ROTATE_TIME調(diào)節(jié)葉子旋轉(zhuǎn)快慢
float rotateFraction = ((currentTime - leaf.startTime) % mLeafRotateTime)
/ (float) mLeafRotateTime;
int angle = (int) (rotateFraction * 360);
int rotate = leaf.rotateDirection == 0 ? angle + leaf.rotateAngle : -angle
+ leaf.rotateAngle;
//用矩陣進(jìn)行坐標(biāo)轉(zhuǎn)換
Matrix matrix = new Matrix();
matrix.reset();
matrix.postTranslate(leaf.x, leaf.y);
matrix.postRotate(rotate, leaf.x + mLeafWidth / 2, leaf.y + mLeafHeight / 2);
//對(duì)畫筆設(shè)置Xfermode
outerPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP));
canvas.drawBitmap(mLeafBitmap, matrix, outerPaint);
outerPaint.setXfermode(null);
} else {
continue;
}
}
canvas.restore();
}
//獲取葉子當(dāng)前的位置
public void getLocation(Leaf leaf, long currentTime) {
//計(jì)算當(dāng)前的時(shí)間和葉子繪制的時(shí)間的差值
long intervalTime = currentTime - leaf.startTime;
if (intervalTime < 0) {
//不對(duì)此片葉子進(jìn)行繪制,還沒到它出場(chǎng)的時(shí)間
return;
} else if (intervalTime > mLeafFlyTime) {
//重置葉子的出場(chǎng)時(shí)間
leaf.startTime = System.currentTimeMillis()
+ new Random().nextInt(mLeafFlyTime);
}
float fraction = (float) intervalTime / mLeafFlyTime;
leaf.x = getLeafX(fraction);
leaf.y = getLeafY(leaf);
}
//獲取葉子x坐標(biāo)
public float getLeafX(float fraction) {
return mProgressWidth * (1 - fraction);
}
//獲取葉子y坐標(biāo)莺奔,用到sin函數(shù),多處用到random是為了讓葉子顯的更加自然
public float getLeafY(Leaf leaf) {
float w = (float) (2 * Math.PI / mProgressWidth);
float a = outerRadius / 2;
switch (leaf.type) {
case LOW_AMPLITUDE:
// 小振幅 = 中等振幅 - 振幅差
a = -mAmplitudeDisparity;
break;
case NORMAL_AMPLITUDE:
break;
case HIGH_AMPLITUDE:
// 小振幅 = 中等振幅 + 振幅差
a = +mAmplitudeDisparity;
break;
default:
break;
}
return (float) (a * Math.sin((w * leaf.x))) - mLeafHeight / 2 + outerRadius;
}
最后放上效果圖
可能看著和原著有點(diǎn).......变泄,嘿嘿令哟,原諒我沒有進(jìn)行優(yōu)化,大家看看思路就可以了妨蛹。代碼地址