一步一步實現單身狗雨

Jet啟思發表於2018-08-20

前幾天七夕情人節,街上滿是狗糧,連電影選座也來蹭熱點,被單身狗雨和愛心雨刷屏了。這效果看著不錯,實現起來也不難,決定自己仿一波。

開始之前先上效果圖:

完整效果圖

細節拆分

1、每一個view從頂部落到底部,然後從底部消失。

2、每一個view在X軸上的初始隨機位置開始落下。

3、每一個view隨機初始角度,並且落下過程中旋轉。

4、每一個view初始落下的高度隨機,但最終都會從底部消失。

實現步驟

首先,實現一個view的下落過程,這裡我內建了一個屬性動畫。

// 效果係數
private float rainyFraction;
    
public void startAnimator() {
    if (objectAnimator == null) {
        objectAnimator = ObjectAnimator.ofFloat(this, "rainyFraction", 0, 1);
        objectAnimator.setDuration(6000);
    }
    objectAnimator.start();
}

public float getRainyFraction() {
    return rainyFraction;
}

public void setRainyFraction(float rainyFraction) {
    this.rainyFraction = rainyFraction;
    invalidate();
}
複製程式碼

然後重寫onSizeChanged和onDraw方法,得到整個view的寬高,再來繪製bitmap

float dx;

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    width = w;
    height = h;
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.save();
    // 重置matrix,必須重置不然會疊加
    matrix.reset();
	dx = width/2;
    float dy = height * rainyFraction;
    matrix.postTranslate(width/2, dy);
    canvas.drawBitmap(bitmap, matrix, paint);
    canvas.restore();
}
複製程式碼

下落效果

落下的效果有了,現在考慮在X軸隨機位置的問題,獲取初始X軸位置的方法如下:

// 減去圖片大小是為了不超出
float dx = (float) ((width - imageSize) * Math.random());
複製程式碼

把matrix的dx替換就行了,下來圖片旋轉可以用matrix.postRotate(degrees, px, py)這個方法,計算出初始角度就ok了

// dx和dy都在上面計算過,把這兩句程式碼加上去就好
int angle = (int) (Math.random() * 360);
// 加上落下的係數,就可以實現落下並旋轉了
matrix.postRotate(180 * rainyFraction + angle, imageSize / 2 + dx, imageSize / 2 + dy);
複製程式碼

最後把隨機初始高度的計算也簡單

// 距離頂部的最大高度設為自身高度,獲取隨機值。
float tempHeight = (float) (height * Math.random());
// 這裡的最大高度,就是在頂部的上面的距離,最後圖片的高度,而這個高度+height就是整個落下的總高度,後面做多個圖落下的時候用到
maxHeight = Math.max(tempHeight, maxHeight);
複製程式碼

下落並旋轉

好了,到這裡一個圖的落下就完成了。接下來就是多個圖的效果,怎麼做?for迴圈!這裡我是準備了三個list儲存對應的初始X位置、Y位置以及初始的角度,看程式碼:

public void initCount() {
    widthArr.clear();
    heightArr.clear();
    angles.clear();
    for (int i = 0; i < num; i++) {
        // 隨機橫向x的位置
        widthArr.add((float) ((width - imageSize) * Math.random()));
        float tempHeight = (float) (height * Math.random());
        // 隨機的距離頂部的高度,是負值。加上imageSize是為了保證從頂部開始落下
        heightArr.add(-(imageSize + tempHeight));
        maxHeight = Math.max(tempHeight, maxHeight);
        // 隨機初始角度
        angles.add((int) (Math.random() * 360));
    }
}
複製程式碼

同樣在onDraw方法中也是for循壞使用這些值

for (int i = 0; i < num; i++) {
    // 重置matrix,必須重置不然會疊加
    if (num != widthArr.size()) {
        break;
    }
    matrix.reset();
    // 當前的高度 = 隨機的初始高度(負值) + (view的高度 + 最高的狗頭所在的高度 == 整個動畫的高度)*係數
    // 加上2 * imageSize是為了保證狗頭都落下底部外
    float dy = heightArr.get(i) + (maxHeight + height + 2 * imageSize) * rainyFraction;
    matrix.postTranslate(widthArr.get(i), dy);
    // 以狗頭為中心旋轉
    matrix.postRotate(angles.get(i) + 180 * rainyFraction, imageSize / 2 + widthArr.get(i), imageSize / 2 + dy);
    canvas.drawBitmap(bitmap, matrix, paint);
}
複製程式碼

最後在activity使用RainyView

rainyView.initCount();
rainyView.startAnimator();
複製程式碼

這個效果的完成到這裡就結束了。

寫在最後

這是GitHub地址單身狗雨,完整程式碼可以從上面fork下來。喜歡的給個star唄,謝謝。

另外說一下,這個下落的效果很粗暴,哈哈。而且沒有把落下的速度計算出來,每個view的速度都一樣的。不過這些都沒關係,重要的是學習到裡面的精髓。

相關文章