Android 下雪動畫

這本是一個愉快的季節,但是,呵呵,胡扯! 因爲這篇文章的發表時間是2015年的聖誕節,所以我們需要給Style Android用製造出一些節日氣氛。感謝讀者們,因爲有的讀者可能沒有在慶祝聖誕,有些讀者可能還是6月份。
那麼問題來了,我們應該做些什麼來讓這個節日像是真正的節日呢? 最簡單的方法:帶上聖誕帽,拍個照。

tree

看我多麼歡樂!
但是我感覺這個圖片有些單調,所以來弄點雪花,讓雪花飄下來。
我們可以添加一個包含這個圖片的自定義View

res/layout/activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  tools:context="com.stylingandroid.snowfall.MainActivity">

  <ImageView
    android:id="@+id/image"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_centerInParent="true"
    android:contentDescription="@null"
    android:scaleType="fitCenter"
    android:src="@drawable/tree" />

  <com.stylingandroid.snowfall.SnowView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_alignBottom="@id/image"
    android:layout_alignEnd="@id/image"
    android:layout_alignLeft="@id/image"
    android:layout_alignRight="@id/image"
    android:layout_alignStart="@id/image"
    android:layout_alignTop="@id/image" />
</RelativeLayout>

儘管可以通過繼承ImageView來實現自定義View,但我決定將 SnowView 和圖片分開,這樣每次刷新動畫的時候不用重新渲染圖片,只刷新 SnowView 就行了

SnowView.java

public class SnowView extends View {
    private static final int NUM_SNOWFLAKES = 150;
    private static final int DELAY = 5;

    private SnowFlake[] snowflakes;

    public SnowView(Context context) {
        super(context);
    }

    public SnowView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public SnowView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    protected void resize(int width, int height) {
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.WHITE);
        paint.setStyle(Paint.Style.FILL);
        snowflakes = new SnowFlake[NUM_SNOWFLAKES];
        for (int i = 0; i < NUM_SNOWFLAKES; i++) {
            snowflakes[i] = SnowFlake.create(width, height, paint);
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if (w != oldw || h != oldh) {
            resize(w, h);
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        for (SnowFlake snowFlake : snowflakes) {
            snowFlake.draw(canvas);
        }
        getHandler().postDelayed(runnable, DELAY);
    }

    private Runnable runnable = new Runnable() {
        @Override
        public void run() {
            invalidate();
        }
    };
}

代碼很簡單。 在View 的 onSizeChanged 方法中初始化 150 個隨機位置的雪花對象。 在onDraw方法中畫出雪花,然後每隔一段時間就刷新一下位置,需要注意的是onDraw沒有立即去執行,而是通過創建一個runnable,這樣不會阻塞UI線程
雪花下落是基於Samuel Arbesman的雪花下落的算法

SnowFlake.java

class SnowFlake {
    private static final float ANGE_RANGE = 0.1f;
    private static final float HALF_ANGLE_RANGE = ANGE_RANGE / 2f;
    private static final float HALF_PI = (float) Math.PI / 2f;
    private static final float ANGLE_SEED = 25f;
    private static final float ANGLE_DIVISOR = 10000f;
    private static final float INCREMENT_LOWER = 2f;
    private static final float INCREMENT_UPPER = 4f;
    private static final float FLAKE_SIZE_LOWER = 7f;
    private static final float FLAKE_SIZE_UPPER = 20f;

    private final Random random;
    private final Point position;
    private float angle;
    private final float increment;
    private final float flakeSize;
    private final Paint paint;

    public static SnowFlake create(int width, int height, Paint paint) {
        Random random = new Random();
        int x = random.getRandom(width);
        int y = random.getRandom(height);
        Point position = new Point(x, y);
        float angle = random.getRandom(ANGLE_SEED) / ANGLE_SEED * ANGE_RANGE + HALF_PI - HALF_ANGLE_RANGE;
        float increment = random.getRandom(INCREMENT_LOWER, INCREMENT_UPPER);
        float flakeSize = random.getRandom(FLAKE_SIZE_LOWER, FLAKE_SIZE_UPPER);
        return new SnowFlake(random, position, angle, increment, flakeSize, paint);
    }

    SnowFlake(Random random, Point position, float angle, float increment, float flakeSize, Paint paint) {
        this.random = random;
        this.position = position;
        this.angle = angle;
        this.increment = increment;
        this.flakeSize = flakeSize;
        this.paint = paint;
    }

    private void move(int width, int height) {
        double x = position.x + (increment * Math.cos(angle));
        double y = position.y + (increment * Math.sin(angle));

        angle += random.getRandom(-ANGLE_SEED, ANGLE_SEED) / ANGLE_DIVISOR;

        position.set((int) x, (int) y);

        if (!isInside(width, height)) {
            reset(width);
        }
    }

    private boolean isInside(int width, int height) {
        int x = position.x;
        int y = position.y;
        return x >= -flakeSize - 1 && x + flakeSize <= width && y >= -flakeSize - 1 && y - flakeSize < height;
    }

    private void reset(int width) {
        position.x = random.getRandom(width);
        position.y = (int) (-flakeSize - 1);
        angle = random.getRandom(ANGLE_SEED) / ANGLE_SEED * ANGE_RANGE + HALF_PI - HALF_ANGLE_RANGE;
    }

    public void draw(Canvas canvas) {
        int width = canvas.getWidth();
        int height = canvas.getHeight();
        move(width, height);
        canvas.drawCircle(position.x, position.y, flakeSize, paint);
    }
}

初始化的時候,雪花的隨機位置就已經確定了。這是爲了確保雪花不會每次畫的時候都在開始的位置。當一個雪花的位置超出Canvas的邊界之後,它就會被重新放到頂部的一個隨機位置,這樣就可以循環利用了,避免了重複創建。
當畫雪花下落的每一幀的時候,我們首先給SnowFlake添加一個隨機數來改變位置,這樣可以模仿有小風吹雪花。
在把雪花畫到canvas上之前,我們會進行邊界檢查(如果需要的話,超出邊界的就重新放到頂部)

我一直在不斷的調整裏面的常量來改變下雪的效果直到我感覺滿意爲止。

最終效果如下:

width="880" height="660" src="https://www.youtube.com/embed/pk66ZziTfOw" allowfullscreen="">

當然了,在canvas裏面塞這麼多東西不是一個好的方法(有其他更好的 比如OpenGL),但是,我現在要去吃火雞了,所以可能要等下一次了。

源文件地址

版權聲明:
Part of this code is based upon “Snowfall” by Sam Arbesman, licensed under Creative Commons Attribution-Share Alike 3.0 and GNU GPL license.
Work: http://openprocessing.org/visuals/?visualID= 84771
License:
http://creativecommons.org/licenses/by-sa/3.0/
http://creativecommons.org/licenses/GPL/2.0/

© 2015, Mark Allison. All rights reserved. This article originally appeared on Styling Android.

Portions of this page are modifications based on work created and shared by Google and used according to terms described in the Creative Commons 3.0 Attribution License

發佈了272 篇原創文章 · 獲贊 59 · 訪問量 43萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章