效果
開始前先做個熱身( ˘•灬•˘ )
自己實現比較容易,但是到了要出部落格整理思路,總結要點的時候就撓頭,不知雲所以,所以最簡單的還是 Read the fucking source code
如果對安卓UI有興趣的朋友可以加我好友互相探討,這裡有很多自定義view可以參考
思路
思路比較簡單,整個view無非兩樣東西
- 雲
- 雨滴
這裡又包含兩部分動畫,一部分是雲的左右移動動畫,一部分是雨滴移動動畫 那我們這裡可以自定義一些屬性,如果對自定義屬性還不太瞭解的同學,搜下百度哈
<resources>
<declare-styleable name="RainyView">
<!--雨滴的顏色-->
<attr name="raindrop_color" format="color"></attr>
<!--左邊雲的顏色-->
<attr name="left_cloud_color" format="color"></attr>
<!--右邊雲的顏色-->
<attr name="right_cloud_color" format="color"></attr>
<!-可同時存在的雨滴的最大數量-->
<attr name="raindrop_max_number" format="integer"></attr>
<!--每個雨滴之間建立的時間間隔-->
<attr name="raindrop_creation_interval" format="integer"></attr>
<!--每個雨滴的最小長度-->
<attr name="raindrop_min_length" format="integer"></attr>
<!--每個雨滴的最大長度-->
<attr name="raindrop_max_length" format="integer"></attr>
<!--雨滴的大小-->
<attr name="raindrop_size" format="integer"></attr>
<!--雨滴的最小移動速度-->
<attr name="raindrop_min_speed" format="float"></attr>
<!--雨滴的最大移動速度-->
<attr name="raindrop_max_speed" format="float"></attr>
<!--雨滴的斜率-->
<attr name="raindrop_slope" format="float"></attr>
</declare-styleable>
</resources>
複製程式碼
畫雲
雲怎麼畫?
雲的形狀不可勝舉,我這裡只實現了一種簡單的形狀:
那我們如何通過畫筆將其畫出來:
1.首先,我們先畫底部,底部是一個圓角的矩形,通過下面方法繪製新增圓角矩形
path.addRoundRect(RectF rect, float rx, float ry, Direction dir)
2.在該圓角的矩形的基礎上,再畫兩個圓,左邊的為小圓,右邊的為大圓,這樣就產生了一個最簡單的雲的圖形,
在設定了以下程式碼之後
paint.setStyle(Paint.Style.FILL);
複製程式碼
雲的效果如下:
我們把這個雲作為左邊的雲,那麼右邊的雲怎麼畫?
很簡單,因為我們這裡用path來裝載了這個雲的路徑,通過以下方法,
mComputeMatrix.preTranslate(rightCloudTranslateX, -calculateRect.height() * (1 - CLOUD_SCALE_RATIO) / 2);
mComputeMatrix.postScale(CLOUD_SCALE_RATIO, CLOUD_SCALE_RATIO, rightCloudCenterX, leftCloudEndY);
mLeftCloudPath.transform(mComputeMatrix, mRightCloudPath);
複製程式碼
將這個雲的path移動,縮小,並將其路徑轉換到mRightCloudPath即可
在onDraw()的時候,呼叫以下方法就可以描繪路徑了
canvas.drawPath()
複製程式碼
接下來我們來實現雲的動畫,我們由上面已經瞭解到:
/**
* Transform the points in this path by matrix, and write the answer
* into dst. If dst is null, then the the original path is modified.
*
* @param matrix The matrix to apply to the path
* @param dst The transformed path is written here. If dst is null,
* then the the original path is modified
*/
public void transform(Matrix matrix, Path dst) {
long dstNative = 0;
if (dst != null) {
dst.isSimplePath = false;
dstNative = dst.mNativePath;
}
nTransform(mNativePath, matrix.native_instance, dstNative);
}
複製程式碼
該方法可以將一個path進行matrix轉換,即矩陣轉換,因此我們可以通過方法matrix.postTranslate來實現平移動畫,即建立一個迴圈動畫,通過postTranslate來設定動畫值就可以了,這裡左邊的雲在右邊的雲之上,因此先畫右邊的雲。
mComputeMatrix.reset();
mComputeMatrix.postTranslate((mMaxTranslationX / 2) * mRightCloudAnimatorValue, 0);
mRightCloudPath.transform(mComputeMatrix, mComputePath);
canvas.drawPath(mComputePath, mRightCloudPaint);
mComputeMatrix.reset();
mComputeMatrix.postTranslate(mMaxTranslationX * mLeftCloudAnimatorValue, 0);
mLeftCloudPath.transform(mComputeMatrix, mComputePath);
canvas.drawPath(mComputePath, mLeftCloudPaint);
複製程式碼
畫雨滴
首先我們要知道一點是,所有的雨滴都是隨機產生的,而產生的值,可以根據上面的自定義屬性指定,也可以使用自定義的值,我們先定義一個雨滴類
private class RainDrop{
float speedX; //雨滴x軸移動速度
float speedY; //雨滴y軸移動速度
float xLength; //雨滴的x軸長度
float yLength; //雨滴的y軸長度
float x; //雨滴的x軸座標
float y; //雨滴的y軸座標
float slope; //雨滴的斜率
}
複製程式碼
關於上面引數,這裡畫張圖來示例:
關於斜率 我這裡開放了一個設定斜率的介面,代表雨滴的一個傾斜度,可以看到下圖的雨滴都是傾斜的,就是通過斜率來設定這個傾斜度
該直線的斜率為k=(y1-y2)/(x1-x2)斜率:表示一條直線(或曲線的切線)關於(橫)座標軸傾斜程度的量。它通常用直線(或曲線的切線)與(橫)座標軸夾角的正切,或兩點的縱座標之差與橫座標之差的比來表示。
我這裡使用了固定的斜率,使所有的雨滴方向一致,如果想將其改為隨機值的同學,可以下載原始碼自行修改。
在建立雨滴物件的時候,以下步驟使我們需要做的:
- 斜率賦值(我這裡是指定的,因此不用計算隨機斜率)
- 計算x軸、y軸移動速度隨機值
- 計算雨滴長度隨機值(同時計算x軸,y軸長度值)
- 計算x,y座標隨機值(為了營造雨滴更好的出場效果,這裡設定了y軸的起點座標為y-雨滴y軸長度)
建立雨滴物件後,我們有了想要的引數,我們可以canvas.drawLine來畫雨滴
canvas.drawLine(rainDrop.x, rainDrop.y,
rainDrop.slope > 0 ? rainDrop.x + rainDrop.xLength : rainDrop.x - rainDrop.xLength,
rainDrop.y + rainDrop.yLength,
mRainPaint);
複製程式碼
這裡需要注意以下,為什麼canvas.drawLine中的stopX引數要設定為
rainDrop.slope > 0 ? rainDrop.x + rainDrop.xLength : rainDrop.x - rainDrop.xLength
複製程式碼
這是因為,我們的雨滴是一直往下移動即y是增加的,我們上面知道斜率公式為: k=(y1-y2)/(x1-x2)
即y1-y2肯定是大於0的,因此
當斜率小於0的時候,雨滴是這樣的,即x1-x2 < 0
當斜率大於0的時候,雨滴是這樣的,即x1-x2 > 0
雨滴動畫,由於每一個雨滴物件引數已經定義,在進行動畫的時候,只需要根據速度,設定x、y軸的下一個點的座標就行了
if (rainDrop.slope >= 0) {
rainDrop.x += rainDrop.speedX;
}else{
rainDrop.x -= rainDrop.speedX;
}
rainDrop.y += rainDrop.speedY;
複製程式碼
優化
我們知道,頻繁的建立雨滴的時候,如果每次都建立新物件的話, 可能會增加不必要的記憶體使用,而且很容易引起頻繁的gc,甚至是記憶體抖動。
因此這裡我增加了一個回收功能
//首先判斷棧中是否存在回收的物件,若存在,則直接複用,若不存在,則建立一個新的物件
private RainDrop obtainRainDrop(){
if (mRecycler.isEmpty()){
return new RainDrop();
}
return mRecycler.pop();
}
//回收到一個棧裡面,若這個棧數量超過最大可顯示數量,則pop
private void recycle(RainDrop rainDrop){
if (rainDrop == null){
return;
}
if (mRecycler.size() >= mRainDropMaxNumber){
mRecycler.pop();
}
mRecycler.push(rainDrop);
}
複製程式碼
開源不易,請尊重作者勞動,轉載註明出處
歡迎Github follow,star以表激勵。