編輯:Android編程入門
實現雪花的效果其實也可以通過自定義View的方式來實現的(SurfaceView也是繼承自View的),而且操作上也相對簡單一些,當然也有一些不足啦...
相對於View,SurfaceView有如下特點:
(1)SurfaceView可以直接獲取Canvas對象,在非UI線程裡也可以進行繪制;
(2)SurfaceView支持雙緩沖技術,具有更高的繪圖效率;
(3)Surface系列產品也火了一陣子了,用Surface准沒錯.....(好吧,我承認微軟給了我很大一筆廣告費....想象ing...)
先上圖:
(1)原圖:
a.雪花(snow_flake.png),由於是白色的所以看不見(虛線之間)
------
------
b.背景(snow_bg0.png)

(2) 效果截圖(雪花的大小、數量、下落速度等都是可通過xml屬性調節的):

下面開始實現自定義SurfaceView...
1. 首先確定一片雪花所需要的參數:長、寬、在屏幕上的坐標、下落的水平/垂直速度....恩先這些吧,把它們封裝到一個類裡面:
public class SnowFlake {
private int mWidth;
private int mHeight;
private int mX;
private int mY;
private int mSpeedX;
private int mSpeedY;
public int getHeight() {
return mHeight;
}
public void setHeight(int height) {
this.mHeight = height;
}
public int getSpeedX() {
return mSpeedX;
}
public void setSpeedX(int speedX) {
this.mSpeedX = mSpeedX;
}
public int getSpeedY() {
return mSpeedY;
}
public void setSpeedY(int speedY) {
this.mSpeedY = speedY;
}
public int getWidth() {
return mWidth;
}
public void setWidth(int width) {
this.mWidth = width;
}
public int getX() {
return mX;
}
public void setX(int x) {
this.mX = x;
}
public int getY() {
return mY;
}
public void setY(int y) {
this.mY = y;
}
}
2. 在res/values下新建 attrs.xml 文件,自定義幾個屬性值:雪花的數量、最大/ 小尺寸、下落速度、資源圖片等,更改如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="flakeCount" format="integer"/>
<attr name="minSize" format="integer"/>
<attr name="maxSize" format="integer"/>
<attr name="flakeSrc" format="reference|integer"/>
<attr name="speedX" format="integer"/>
<attr name="speedY" format="integer"/>
<declare-styleable name="Snow">
<attr name="flakeCount"/>
<attr name="minSize"/>
<attr name="maxSize"/>
<attr name="flakeSrc"/>
<attr name="speedX"/>
<attr name="speedY"/>
</declare-styleable>
</resources>
3. 下面輪到SurfaceView出場...啊不...是SurfaceView的son出場了........
(1)定義名稱為Snow的類,擴展SurfaceView,並實現接口 SurfaceHolder.Callback,代碼如下:
public class Snow extends SurfaceView implements SurfaceHolder.Callback {
public Snow(Context context) {
this(context, null);
}
public Snow(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public Snow(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
}
(2)添加以下變量,初始化默認值:
private SurfaceHolder mHolder;
private SnowFlake[] mFlakes;
private int mViewWidth = 200;
private int mViewHeight = 100;
private int mFlakeCount = 20;
private int mMinSize = 50;
private int mMaxSize = 70;
private int mSpeedX = 10;
private int mSpeedY = 20;
private Bitmap mSnowBitmap = null;
private boolean mStart = false;
(3)在構造函數中獲取控件屬性值,並初始化 SurfaceHolder (注意我們只需在最後一個構造函數實現即可,前面的兩個通過this來調用此構造函數):
public Snow(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initHolder();
setZOrderOnTop(true);
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.Snow, defStyleAttr, 0);
int cnt = array.getIndexCount();
for (int i = 0; i < cnt; i++) {
int attr = array.getIndex(i);
switch (attr) {
case R.styleable.Snow_flakeCount:
mFlakeCount = array.getInteger(attr, 0);
break;
case R.styleable.Snow_minSize:
mMinSize = array.getInteger(attr, 50);
break;
case R.styleable.Snow_maxSize:
mMaxSize = array.getInteger(attr, 70);
break;
case R.styleable.Snow_flakeSrc:
Integer srcId = array.getResourceId(attr, R.drawable.snow_flake);
mSnowBitmap = BitmapFactory.decodeResource(getResources(), srcId);
break;
case R.styleable.Snow_speedX:
mSpeedX = array.getInteger(attr, 10);
break;
case R.styleable.Snow_speedY:
mSpeedY = array.getInteger(attr, 10);
break;
default:
break;
}
}
if (mMinSize > mMaxSize) {
mMaxSize = mMinSize;
}
array.recycle();
}
初始化 SurfaceHolder 部分:
private void initHolder() {
mHolder = this.getHolder();
mHolder.setFormat(PixelFormat.TRANSLUCENT);
mHolder.addCallback(this);
}
(4)在Snow類中添加如下變量,並重寫 onMeasure() 函數,測量SurfaceView的大小:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//--- measure the view's width
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
if (widthMode == MeasureSpec.EXACTLY) {
mViewWidth = MeasureSpec.getSize(widthMeasureSpec);
} else {
mViewWidth = (getPaddingStart() + mSnowBitmap.getWidth() + getPaddingEnd());
}
//--- measure the view's height
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (heightMode == MeasureSpec.EXACTLY) {
mViewHeight = MeasureSpec.getSize(heightMeasureSpec);
} else {
mViewHeight = (getPaddingTop() + mSnowBitmap.getHeight() + getPaddingBottom());
}
setMeasuredDimension(mViewWidth, mViewHeight);
}
(5)初始化snow flakes的函數:通過隨機數生成一定范圍內的坐標值和snow flake 的大小值,一開始時雪花是在屏幕上方的:
private void initSnowFlakes() {
mFlakes = new SnowFlake[mFlakeCount];
boolean isRightDir = new Random().nextBoolean();
for (int i = 0; i < mFlakes.length; i++) {
mFlakes[i] = new SnowFlake();
mFlakes[i].setWidth(new Random().nextInt(mMaxSize-mMinSize) + mMinSize);
mFlakes[i].setHeight(mFlakes[i].getWidth());
mFlakes[i].setX(new Random().nextInt(mViewWidth));
mFlakes[i].setY(-(new Random().nextInt(mViewHeight)));
mFlakes[i].setSpeedY(new Random().nextInt(4) + mSpeedY);
if (isRightDir) {
mFlakes[i].setSpeedX(new Random().nextInt(4) + mSpeedX);
}
else {
mFlakes[i].setSpeedX(-(new Random().nextInt(4) + mSpeedX));
}
}
}
(6)繪制snow flakes 的函數:通過SurfaceHolder 的lockCanvas()函數獲取到畫布,繪制完後再調用 unlockCanvasAndPost() 函數釋放canvas並將緩沖區繪制的內容一次性繪制到canvas上:
private void drawView() {
if (mHolder == null) {
return;
}
Canvas canvas = mHolder.lockCanvas();
if (canvas == null) {
return;
}
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
drawSnow(canvas);
mHolder.unlockCanvasAndPost(canvas);
}
private void drawSnow(Canvas canvas) {
Rect rect = new Rect();
Paint paint = new Paint();
for (SnowFlake flake : mFlakes) {
rect.left = flake.getX();
rect.top = flake.getY();
rect.right = rect.left + flake.getWidth();
rect.bottom = rect.top + flake.getHeight();
canvas.drawBitmap(mSnowBitmap, null, rect, paint);
}
}
(7)更新snow flakes的參數的函數:
private void updatePara() {
int x;
int y;
for (SnowFlake flake : mFlakes) {
if (flake == null) {
break;
}
x = flake.getX() + flake.getSpeedX();
y = flake.getY() + flake.getSpeedY();
if ((x > mViewWidth + 20 || x < 0)
|| (y > mViewHeight + 20)) {
x = new Random().nextInt(mViewWidth);
y = 0;
}
flake.setX(x);
flake.setY(y);
}
}
(8)開啟繪畫線程的 start 函數:
public void start() {
new Thread(){
@Override
public void run() {
while (true) {
try {
if (mStart) {
updatePara();
drawView();
}
Thread.sleep(20);
}
catch (Exception ex) {
ex.printStackTrace();
}
}
}
}.start();
}
(9)修改 surfaceCreated(SurfaceHolder holder) 函數,即在SurfaceView創建完成後初始化snow flakes,並開啟動畫線程:
@Override
public void surfaceCreated(SurfaceHolder holder) {
initSnowFlakes();
start();
}
(10)重寫 onVisibilityChanged() 函數,在控件不可見時停止更新和繪制控件,避免CPU資源浪費:
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
mStart = (visibility == VISIBLE);
}
4. 控件的使用:
由於我們做了很多封裝工作,所以控件使用是很簡單的, 在布局文件中添加並設置對應屬性即可:
<com.haoye.snow.Snow
android:layout_width="match_parent"
android:layout_height="match_parent"
myview:flakeCount="30"
myview:minSize="30"
myview:maxSize="70"
myview:speedX="5"
myview:speedY="10"
myview:flakeSrc="@drawable/snow_flake"/>
--------------------------
至此,自定義SurfaceView控件就完成了,當然我們還可以添加一些其他的效果,比如讓隨機生成的雪花小片的多一些,適當調節雪花的亮度等,這樣可以更好地模擬遠處下雪的情景,使景色具有深度。
另外在上面的代碼實現中,其實通過
mHolder.setFormat(PixelFormat.TRANSLUCENT);
setZOrderOnTop(true);
這兩行代碼,我們已經將SurfaceView設置為背景透明的模式,在每次繪制的時候,通過
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
這行代碼清理屏幕後再重新繪制;這使得我們可以在控件外添加背景圖片,而不需要每次都在控件中重繪。
源碼下載:https://github.com/laishenghao/Snow/
android開發-界面設計基本知識
一個好的APP不僅有美觀,好看的界面,更需要良好的性能和穩定性。作為一名開發人員,需要理解界面設計原則並寫出優秀的界面設計代碼。本章主要講述基本控件的使用,界面布局及一些
java/android線程池詳解
一,簡述線程池:線程池是如何工作的:一系列任務出現後,根據自己的線程池安排任務進行。如圖: 線程池的好處:重用線程池中的線程,避免因為線程的創建和銷毀所帶來的性能開銷。能
Android Activity的4種啟動模式詳解(示例)
轉載請注明出處:http://hovertree.com/先介紹下Android對Activity的管理,Android采用Task來管理多個Activity,當我們啟動
Android 架構
Android 架構Android 操作系統是一個軟件組件的棧,在架構圖中它大致可以分為五個部分和四個主要層。Linux內核在所有層的最底下是 Linux