前天的瀏覽 GitHub 時發現一個模仿 Gif 的 Loading 特效的項目,感受效果很不錯,也比較有創意,以下:java
GitHub 上好幾個作這個效果的項目,可是不多有徹底實現的,有的還有 Bug,因而花了 2 天實現了一下。git
效果以下:github
GitHub 項目在這裏 LeavesLoadingcanvas
實現要求:segmentfault
葉子dom
風扇ide
細節函數
本質是事先產生必定數量葉子,這些葉子的漂動時的振幅、相位、旋轉方向等等都是隨機的,而且飄動是週期性地即葉子飄動到最左邊時,又從新回到最右邊。post
Leaf 類:動畫
private class Leaf{ float x,y;//座標 AmplitudeType type;//葉子飄動振幅 int rotateAngle;//旋轉角度 RotateDir rotateDir;//旋轉方向 long startTime;//起始時間 int n;//初始相位 }
Leaf 生成方法:
Leaf generateLeaf(){ Leaf leaf = new Leaf(); //隨機振幅 int randomType = mRandom.nextInt(3); switch (randomType){ case 0: //小振幅 leaf.type = AmplitudeType.LITTLE; break; case 1: //中等振幅 leaf.type = AmplitudeType.MIDDLE; break; default: //大振幅 leaf.type = AmplitudeType.BIG; break; } //隨機旋轉方向 int dir = mRandom.nextInt(2); switch (dir){ case 0: //逆時針 leaf.rotateDir = RotateDir.ANTICLOCKWISE; break; default: //順時針 leaf.rotateDir = RotateDir.CLOCKWISE; break; } //隨機起始角度 leaf.rotateAngle = mRandom.nextInt(360); leaf.n = mRandom.nextInt(20); mAddTime += mRandom.nextInt((int)mLeafFloatTime); leaf.startTime = System.currentTimeMillis() + mAddTime; return leaf; }
肯定 Leaf 在某個時刻的座標 ( x , y ):
/** * 獲取葉子的(x,y)位置 * @param leaf 葉子 * @param currentTime 當前時間 */ private void getLeafLocation(Leaf leaf,long currentTime){ long intervalTime = currentTime - leaf.startTime;//飄動時長 if (intervalTime <= 0){ // 此 Leaf 還沒到飄動時間 return; }else if (intervalTime > mLeafFloatTime){ // Leaf 的飄動時間大於指定的飄動時間,即葉子飄動到了最左邊,應回到最右邊 leaf.startTime = currentTime + new Random().nextInt((int)mLeafFloatTime); } // 計算移動因子 float fraction = (float) intervalTime / mLeafFloatTime; leaf.x = (1-fraction)*mProgressLen; leaf.y = getLeafLocationY(leaf); if (leaf.x <= mYellowOvalHeight / 4){ //葉子飄到最左邊,有可能會超出 RoundRect 邊界,因此提早特殊處理 leaf.startTime = currentTime + new Random().nextInt((int)mLeafFloatTime); leaf.x = mProgressLen; leaf.y = getLeafLocationY(leaf); } }
要想讓 Leaf 飄動軌跡爲正弦函數,關鍵在於肯定 Leaf 的 Y 軸座標:
/** * 獲取葉子的Y軸座標 * @param leaf 葉子 * @return 通過計算的葉子Y軸座標 */ private float getLeafLocationY(Leaf leaf){ float w = (float) (Math.PI * 2 / mProgressLen);//角頻率 float A;//計算振幅值 switch (leaf.type){ case LITTLE: A = mLeafLen/3; break; case MIDDLE: A = mLeafLen*2/3; break; default: A = mLeafLen; break; } // (mHeight-mLeafLen)/2 是爲了讓 Leaf 的Y軸起始位置居中 return (float) (A * Math.sin(w * leaf.x + leaf.n)+(mHeight-mLeafLen)/2); }
這裏就涉及到了 Leaf 的繪製,其實 Gif 中的葉子和風扇均可以使用 Canves 直接繪製圖案,可是這樣就會有兩個問題:
所以這裏採用 Canves.drawBitmap()
的方式繪製,直接使用已有的圖片做爲葉子和風扇,同時利用 Canves.drawBitmap()
的一個重載的方法能夠很方便的實現旋轉、縮放、平移:
void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) ;
就是經過這裏的 Matrix 矩陣,它內部封裝了 postScale()
、postTranslate
、postRotate()
等方法,能夠幫助咱們快速的對 Bitmap 進行旋轉、縮放、平移還有其餘操做。使用時要記得配合 Canves 的 save()
和 restore()
使用,不然達不到想要的效果。
對這方面不熟的朋友能夠看看 HenCoder 的自定義 View 教學 1-4 。
繪製 Leaf 的方法:
private void drawLeaves(Canvas canvas){ long currentTime = System.currentTimeMillis(); for (Leaf leaf : mLeafList) { if (currentTime > leaf.startTime && leaf.startTime != 0){ // 獲取 leaf 當前的座標 getLeafLocation(leaf,currentTime); canvas.save(); Matrix matrix = new Matrix(); // 縮放 自適應 View 的大小 float scaleX = (float) mLeafLen / mLeafBitmapWidth; float scaleY = (float) mLeafLen / mLeafBitmapHeight; matrix.postScale(scaleX,scaleY); // 位移 float transX = leaf.x; float transY = leaf.y; matrix.postTranslate(transX,transY); // 旋轉 // 計算旋轉因子 float rotateFraction = ((currentTime - leaf.startTime) % mLeafRotateTime) /(float)mLeafRotateTime; float rotate; switch (leaf.rotateDir){ case CLOCKWISE: //順時針 rotate = rotateFraction * 360 + leaf.rotateAngle; break; default: //逆時針 rotate = -rotateFraction * 360 + leaf.rotateAngle; break; } // 旋轉中心選擇 Leaf 的中心座標 matrix.postRotate(rotate,transX + mLeafLen / 2,transY + mLeafLen / 2); canvas.drawBitmap(mLeafBitmap,matrix,mBitmapPaint); canvas.restore(); } }
增長一個判斷字段 isLoadingCompleted ,在 onDraw()
中選擇對應繪製策略。
isLoadingCompleted 在 setProgress()
中根據 progress 設置:
/** * 設置進度(自動刷新) * @param progress 0-100 */ public void setProgress(int progress){ if (progress < 0){ mProgress = 0; }else if (progress > 100){ mProgress = 100; }else { mProgress = progress; } if (progress == 100){ isLoadingCompleted = true; }else { isLoadingCompleted = false; } // 255 不透明 mCompletedFanPaint.setAlpha(255); postInvalidate(); }
LeavesLoading.onDraw()
部分實現:
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); ...... if (isLoadingCompleted){ //繪製加載完成特效 drawCompleted(canvas); }else { //繪製扇葉 drawFan(canvas,mFanLen,mBitmapPaint); } //刷新 postInvalidate(); }
drawCompleted()
實現:
private void drawCompleted(Canvas canvas) { // 每次繪製風扇透明度遞減10 int alpha = mCompletedFanPaint.getAlpha() - 10; if (alpha <= 0){ alpha = 0; } mCompletedFanPaint.setAlpha(alpha); // 文字透明度恰好與風扇相反 mCompletedTextPaint.setAlpha(255-alpha); // 計算透明因子 float fraction = alpha / 255f; // 葉片大小 和 文字大小 也是相反變化的 float fanLen = fraction * mFanLen; float textSize = (1 - fraction) * mCompletedTextSize; mCompletedTextPaint.setTextSize(textSize); //測量文字佔用空間 Rect bounds = new Rect(); mCompletedTextPaint.getTextBounds( LOADING_COMPLETED, 0, LOADING_COMPLETED.length(), bounds); // 與 drawLeaf() 類似,再也不贅述 drawFan(canvas, (int) fanLen, mCompletedFanPaint); //畫文字 canvas.drawText( LOADING_COMPLETED, 0, LOADING_COMPLETED.length(), mFanCx-bounds.width()/2f, mFanCy+bounds.height()/2f, mCompletedTextPaint); }
流程:計算風扇和文字透明度 -> 計算風扇和文字大小以及文字佔用空間 -> 繪製 ,風扇逐漸變透明和變小,文字逐漸變清晰和變大,註釋寫得比較清楚就不贅述了。
文章中若有出現任何錯誤,歡迎你們到評論區留言指正。
若是以爲 LeavesLoading 對您有任何幫助,但願能夠在 GitHub 獲得您的 Star !
Thanks: