Android 自定義View之下雨動畫

效果

RainyView

RainyView

開始前先作個熱身( ˘•灬•˘ )git

本身實現比較容易,可是到了要出博客整理思路,總結要點的時候就撓頭,不知雲因此,因此最簡單的仍是 Read the fucking source codegithub

若是對安卓UI有興趣的朋友能夠加我好友互相探討,這裏有不少自定義view能夠參考canvas

思路

思路比較簡單,整個view無非兩樣東西bash

  • 雨滴

這裏又包含兩部分動畫,一部分是雲的左右移動動畫,一部分是雨滴移動動畫 那咱們這裏能夠自定義一些屬性,若是對自定義屬性還不太瞭解的同窗,搜下百度哈app

<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>
複製代碼

畫雲

雲怎麼畫?post

雲的形狀不可勝舉,我這裏只實現了一種簡單的形狀: 優化

RainyView

那咱們如何經過畫筆將其畫出來:動畫

1.首先,咱們先畫底部,底部是一個圓角的矩形,經過下面方法繪製添加圓角矩形 path.addRoundRect(RectF rect, float rx, float ry, Direction dir) ui

RainyView

2.在該圓角的矩形的基礎上,再畫兩個圓,左邊的爲小圓,右邊的爲大圓,這樣就產生了一個最簡單的雲的圖形, this

RainyView

在設置瞭如下代碼以後

paint.setStyle(Paint.Style.FILL);
複製代碼

雲的效果以下:

RainyView

咱們把這個雲做爲左邊的雲,那麼右邊的雲怎麼畫?

很簡單,由於咱們這裏用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; //雨滴的斜率
}
複製代碼

關於上面參數,這裏畫張圖來示例:

RainyView

關於斜率 我這裏開放了一個設置斜率的接口,表明雨滴的一個傾斜度,能夠看到下圖的雨滴都是傾斜的,就是經過斜率來設置這個傾斜度

RainyView

斜率:表示一條直線(或曲線的切線)關於(橫)座標軸傾斜程度的量。它一般用直線(或曲線的切線)與(橫)座標軸夾角的正切,或兩點的縱座標之差與橫座標之差的比來表示。

RainyView
該直線的斜率爲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

RainyView

當斜率大於0的時候,雨滴是這樣的,即x1-x2 > 0

RainyView

雨滴動畫,因爲每個雨滴對象參數已經定義,在進行動畫的時候,只須要根據速度,設置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以表激勵。

Github

相關文章
相關標籤/搜索