Android下雪動畫的實現
這本是一個愉快的季節,但是,呵呵,胡扯! 因為這篇文章的發表時間是2015年的聖誕節,所以我們需要給Style Android用製造出一些節日氣氛。感謝讀者們,因為有的讀者可能沒有在慶祝聖誕,有些讀者可能還是6月份。
那麼問題來了,我們應該做些什麼來讓這個節日像是真正的節日呢? 最簡單的方法:帶上聖誕帽,拍個照。
但是我感覺這個圖片有些單調,所以來弄點雪花,讓雪花飄下來。
我們可以添加一個包含這個圖片的自訂View
res/layout/activity_main.xml
<code class="hljs xml"><!--{cke_protected}{C}%3C!%2D%2D%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%2D%2D%3E--><relativelayout android:layout_height="match_parent" android:layout_width="match_parent" tools:context="com.stylingandroid.snowfall.MainActivity" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <imageview android:contentdescription="@null" android:id="@+id/image" android:layout_centerinparent="true" android:layout_height="match_parent" android:layout_width="match_parent" android:scaletype="fitCenter" android:src="@drawable/tree"> <com.stylingandroid.snowfall.snowview 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" android:layout_height="match_parent" android:layout_width="match_parent"></com.stylingandroid.snowfall.snowview></imageview></relativelayout></code>
儘管可以通過繼承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上之前,我們會進行邊界檢查(如果需要的話,超出邊界的就重新放到頂部)
我一直在不斷的調整裡面的常量來改變下雪的效果直到我感覺滿意為止。
當然了,在canvas裡面塞這麼多東西不是一個好的方法(有其他更好的 比如OpenGL),但是,我現在要去吃火雞了,所以可能要等下一次了。