- 原文鏈接 : Snowfall
- 原文作者 : Styling Android
- 譯文出自 : hanks.xyz
- 譯者 : hanks-zyh
- 校對者: desmond1121
- 狀態 : 完成
這本是一個愉快的季節,但是,呵呵,胡扯! 因爲這篇文章的發表時間是2015年的聖誕節,所以我們需要給Style Android用製造出一些節日氣氛。感謝讀者們,因爲有的讀者可能沒有在慶祝聖誕,有些讀者可能還是6月份。
那麼問題來了,我們應該做些什麼來讓這個節日像是真正的節日呢? 最簡單的方法:帶上聖誕帽,拍個照。
看我多麼歡樂!
但是我感覺這個圖片有些單調,所以來弄點雪花,讓雪花飄下來。
我們可以添加一個包含這個圖片的自定義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