效果
開始前先做個(gè)熱身( ??灬?? )
自己實(shí)現(xiàn)比較容易,但是到了要出博客整理思路,總結(jié)要點(diǎn)的時(shí)候就撓頭,不知云所以,所以最簡(jiǎn)單的還是
Read the fucking source code
如果對(duì)安卓UI有興趣的朋友可以加我好友互相探討,這里有很多自定義view可以參考
思路
思路比較簡(jiǎn)單,整個(gè)view無非兩樣?xùn)|西
- 云
- 雨滴
這里又包含兩部分動(dòng)畫,一部分是云的左右移動(dòng)動(dòng)畫,一部分是雨滴移動(dòng)動(dòng)畫
那我們這里可以自定義一些屬性,如果對(duì)自定義屬性還不太了解的同學(xué),搜下百度哈
<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>
<!-可同時(shí)存在的雨滴的最大數(shù)量-->
<attr name="raindrop_max_number" format="integer"></attr>
<!--每個(gè)雨滴之間創(chuàng)建的時(shí)間間隔-->
<attr name="raindrop_creation_interval" format="integer"></attr>
<!--每個(gè)雨滴的最小長(zhǎng)度-->
<attr name="raindrop_min_length" format="integer"></attr>
<!--每個(gè)雨滴的最大長(zhǎng)度-->
<attr name="raindrop_max_length" format="integer"></attr>
<!--雨滴的大小-->
<attr name="raindrop_size" format="integer"></attr>
<!--雨滴的最小移動(dòng)速度-->
<attr name="raindrop_min_speed" format="float"></attr>
<!--雨滴的最大移動(dòng)速度-->
<attr name="raindrop_max_speed" format="float"></attr>
<!--雨滴的斜率-->
<attr name="raindrop_slope" format="float"></attr>
</declare-styleable>
</resources>
畫云
云怎么畫?
云的形狀不可勝舉,我這里只實(shí)現(xiàn)了一種簡(jiǎn)單的形狀:
那我們?nèi)绾瓮ㄟ^畫筆將其畫出來:
1.首先,我們先畫底部,底部是一個(gè)圓角的矩形,通過下面方法繪制添加圓角矩形
path.addRoundRect(RectF rect, float rx, float ry, Direction dir)
2.在該圓角的矩形的基礎(chǔ)上,再畫兩個(gè)圓,左邊的為小圓,右邊的為大圓,這樣就產(chǎn)生了一個(gè)最簡(jiǎn)單的云的圖形,
在設(shè)置了以下代碼之后
paint.setStyle(Paint.Style.FILL);
云的效果如下:
我們把這個(gè)云作為左邊的云,那么右邊的云怎么畫?
很簡(jiǎn)單,因?yàn)槲覀冞@里用path來裝載了這個(gè)云的路徑,通過以下方法,
mComputeMatrix.preTranslate(rightCloudTranslateX, -calculateRect.height() * (1 - CLOUD_SCALE_RATIO) / 2);
mComputeMatrix.postScale(CLOUD_SCALE_RATIO, CLOUD_SCALE_RATIO, rightCloudCenterX, leftCloudEndY);
mLeftCloudPath.transform(mComputeMatrix, mRightCloudPath);
將這個(gè)云的path移動(dòng),縮小,并將其路徑轉(zhuǎn)換到mRightCloudPath即可
在onDraw()的時(shí)候,調(diào)用以下方法就可以描繪路徑了
canvas.drawPath()
接下來我們來實(shí)現(xiàn)云的動(dòng)畫,我們由上面已經(jīng)了解到:
/**
* 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);
}
該方法可以將一個(gè)path進(jìn)行matrix轉(zhuǎn)換,即矩陣轉(zhuǎn)換,因此我們可以通過方法matrix.postTranslate來實(shí)現(xiàn)平移動(dòng)畫,即創(chuàng)建一個(gè)循環(huán)動(dòng)畫,通過postTranslate來設(shè)置動(dòng)畫值就可以了,這里左邊的云在右邊的云之上,因此先畫右邊的云。
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);
畫雨滴
首先我們要知道一點(diǎn)是,所有的雨滴都是隨機(jī)產(chǎn)生的,而產(chǎn)生的值,可以根據(jù)上面的自定義屬性指定,也可以使用自定義的值,我們先定義一個(gè)雨滴類
private class RainDrop{
float speedX; //雨滴x軸移動(dòng)速度
float speedY; //雨滴y軸移動(dòng)速度
float xLength; //雨滴的x軸長(zhǎng)度
float yLength; //雨滴的y軸長(zhǎng)度
float x; //雨滴的x軸坐標(biāo)
float y; //雨滴的y軸坐標(biāo)
float slope; //雨滴的斜率
}
關(guān)于上面參數(shù),這里畫張圖來示例:
關(guān)于斜率
我這里開放了一個(gè)設(shè)置斜率的接口,代表雨滴的一個(gè)傾斜度,可以看到下圖的雨滴都是傾斜的,就是通過斜率來設(shè)置這個(gè)傾斜度
斜率:表示一條直線(或曲線的切線)關(guān)于(橫)坐標(biāo)軸傾斜程度的量。它通常用直線(或曲線的切線)與(橫)坐標(biāo)軸夾角的正切,或兩點(diǎn)的縱坐標(biāo)之差與橫坐標(biāo)之差的比來表示。
該直線的斜率為k=(y1-y2)/(x1-x2)
我這里使用了固定的斜率,使所有的雨滴方向一致,如果想將其改為隨機(jī)值的同學(xué),可以下載源碼自行修改。
在創(chuàng)建雨滴對(duì)象的時(shí)候,以下步驟使我們需要做的:
- 斜率賦值(我這里是指定的,因此不用計(jì)算隨機(jī)斜率)
- 計(jì)算x軸、y軸移動(dòng)速度隨機(jī)值
- 計(jì)算雨滴長(zhǎng)度隨機(jī)值(同時(shí)計(jì)算x軸,y軸長(zhǎng)度值)
- 計(jì)算x,y坐標(biāo)隨機(jī)值(為了營(yíng)造雨滴更好的出場(chǎng)效果,這里設(shè)置了y軸的起點(diǎn)坐標(biāo)為y-雨滴y軸長(zhǎng)度)
創(chuàng)建雨滴對(duì)象后,我們有了想要的參數(shù),我們可以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參數(shù)要設(shè)置為
rainDrop.slope > 0 ? rainDrop.x + rainDrop.xLength : rainDrop.x - rainDrop.xLength
這是因?yàn)?,我們的雨滴是一直往下移?dòng)即y是增加的,我們上面知道斜率公式為:
k=(y1-y2)/(x1-x2)
即y1-y2肯定是大于0的,因此
當(dāng)斜率小于0的時(shí)候,雨滴是這樣的,即x1-x2 < 0
當(dāng)斜率大于0的時(shí)候,雨滴是這樣的,即x1-x2 > 0
雨滴動(dòng)畫,由于每一個(gè)雨滴對(duì)象參數(shù)已經(jīng)定義,在進(jìn)行動(dòng)畫的時(shí)候,只需要根據(jù)速度,設(shè)置x、y軸的下一個(gè)點(diǎn)的坐標(biāo)就行了
if (rainDrop.slope >= 0) {
rainDrop.x += rainDrop.speedX;
}else{
rainDrop.x -= rainDrop.speedX;
}
rainDrop.y += rainDrop.speedY;
優(yōu)化
我們知道,在頻繁的創(chuàng)建雨滴的時(shí)候,如果每次都創(chuàng)建新對(duì)象的話, 可能會(huì)增加不必要的內(nèi)存使用,而且很容易引起頻繁的gc,甚至是內(nèi)存抖動(dòng)。
因此這里我增加了一個(gè)回收功能
//首先判斷棧中是否存在回收的對(duì)象,若存在,則直接復(fù)用,若不存在,則創(chuàng)建一個(gè)新的對(duì)象
private RainDrop obtainRainDrop(){
if (mRecycler.isEmpty()){
return new RainDrop();
}
return mRecycler.pop();
}
//回收到一個(gè)棧里面,若這個(gè)棧數(shù)量超過最大可顯示數(shù)量,則pop
private void recycle(RainDrop rainDrop){
if (rainDrop == null){
return;
}
if (mRecycler.size() >= mRainDropMaxNumber){
mRecycler.pop();
}
mRecycler.push(rainDrop);
}
開源不易,請(qǐng)尊重作者勞動(dòng),轉(zhuǎn)載注明出處
歡迎Github follow,star以表激勵(lì)。