編輯: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/
一個好的APP不僅有美觀,好看的界面,更需要良好的性能和穩定性。作為一名開發人員,需要理解界面設計原則並寫出優秀的界面設計代碼。本章主要講述基本控件的使用,界面布局及一些
一,簡述線程池:線程池是如何工作的:一系列任務出現後,根據自己的線程池安排任務進行。如圖: 線程池的好處:重用線程池中的線程,避免因為線程的創建和銷毀所帶來的性能開銷。能
轉載請注明出處:http://hovertree.com/先介紹下Android對Activity的管理,Android采用Task來管理多個Activity,當我們啟動
Android 架構Android 操作系統是一個軟件組件的棧,在架構圖中它大致可以分為五個部分和四個主要層。Linux內核在所有層的最底下是 Linux