實現雪花的效果其實也能夠經過自定義View的方式來實現的(SurfaceView也是繼承自View的),並且操做上也相對簡單一些,固然也有一些不足啦...html
相對於View,SurfaceView有以下特色:android
(1)SurfaceView能夠直接獲取Canvas對象,在非UI線程裏也能夠進行繪製;git
(2)SurfaceView支持雙緩衝技術,具備更高的繪圖效率;github
(3)Surface系列產品也火了一陣子了,用Surface準沒錯.....(好吧,我認可微軟給了我很大一筆廣告費....想象ing...)canvas
先上圖:dom
(1)原圖:ide
a.雪花(snow_flake.png),因爲是白色的因此看不見(虛線之間)函數
------------佈局
b.背景(snow_bg0.png)動畫
(2) 效果截圖(雪花的大小、數量、下落速度等都是可經過xml屬性調節的):
下面開始實現自定義SurfaceView...
1. 首先肯定一片雪花所須要的參數:長、寬、在屏幕上的座標、下落的水平/垂直速度....恩先這些吧,把它們封裝到一個類裏面:
1 public class SnowFlake { 2 private int mWidth; 3 private int mHeight; 4 private int mX; 5 private int mY; 6 private int mSpeedX; 7 private int mSpeedY; 8
9 public int getHeight() { 10 return mHeight; 11 } 12
13 public void setHeight(int height) { 14 this.mHeight = height; 15 } 16
17 public int getSpeedX() { 18 return mSpeedX; 19 } 20
21 public void setSpeedX(int speedX) { 22 this.mSpeedX = mSpeedX; 23 } 24
25 public int getSpeedY() { 26 return mSpeedY; 27 } 28
29 public void setSpeedY(int speedY) { 30 this.mSpeedY = speedY; 31 } 32
33 public int getWidth() { 34 return mWidth; 35 } 36
37 public void setWidth(int width) { 38 this.mWidth = width; 39 } 40
41 public int getX() { 42 return mX; 43 } 44
45 public void setX(int x) { 46 this.mX = x; 47 } 48
49 public int getY() { 50 return mY; 51 } 52
53 public void setY(int y) { 54 this.mY = y; 55 } 56 }
2. 在res/values下新建 attrs.xml 文件,自定義幾個屬性值:雪花的數量、最大/ 小尺寸、下落速度、資源圖片等,更改以下:
1 <?xml version="1.0" encoding="utf-8"?> 2 3 <resources> 4 <attr name="flakeCount" format="integer"/> 5 <attr name="minSize" format="integer"/> 6 <attr name="maxSize" format="integer"/> 7 <attr name="flakeSrc" format="reference|integer"/> 8 <attr name="speedX" format="integer"/> 9 <attr name="speedY" format="integer"/> 10 11 <declare-styleable name="Snow"> 12 <attr name="flakeCount"/> 13 <attr name="minSize"/> 14 <attr name="maxSize"/> 15 <attr name="flakeSrc"/> 16 <attr name="speedX"/> 17 <attr name="speedY"/> 18 </declare-styleable> 19 </resources>
3. 下面輪到SurfaceView出場...啊不...是SurfaceView的son出場了........
(1)定義名稱爲Snow的類,擴展SurfaceView,並實現接口 SurfaceHolder.Callback,代碼以下:
1 public class Snow extends SurfaceView implements SurfaceHolder.Callback { 2 public Snow(Context context) { 3 this(context, null); 4 } 5
6 public Snow(Context context, AttributeSet attrs) { 7 this(context, attrs, 0); 8 } 9
10 public Snow(Context context, AttributeSet attrs, int defStyleAttr) { 11 super(context, attrs, defStyleAttr); 12 } 13
14 @Override 15 public void surfaceCreated(SurfaceHolder holder) { 16
17 } 18
19 @Override 20 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 21
22 } 23
24 @Override 25 public void surfaceDestroyed(SurfaceHolder holder) { 26
27 } 28 }
(2)添加如下變量,初始化默認值:
1 private SurfaceHolder mHolder; 2 private SnowFlake[] mFlakes; 3 private int mViewWidth = 200; 4 private int mViewHeight = 100; 5 private int mFlakeCount = 20; 6 private int mMinSize = 50; 7 private int mMaxSize = 70; 8 private int mSpeedX = 10; 9 private int mSpeedY = 20; 10 private Bitmap mSnowBitmap = null; 11 private boolean mStart = false;
(3)在構造函數中獲取控件屬性值,並初始化 SurfaceHolder (注意咱們只需在最後一個構造函數實現便可,前面的兩個經過this來調用此構造函數):
1 public Snow(Context context, AttributeSet attrs, int defStyleAttr) { 2 super(context, attrs, defStyleAttr); 3 initHolder(); 4 setZOrderOnTop(true); 5 6 TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.Snow, defStyleAttr, 0); 7 int cnt = array.getIndexCount(); 8 for (int i = 0; i < cnt; i++) { 9 int attr = array.getIndex(i); 10 switch (attr) { 11 case R.styleable.Snow_flakeCount: 12 mFlakeCount = array.getInteger(attr, 0); 13 break; 14 case R.styleable.Snow_minSize: 15 mMinSize = array.getInteger(attr, 50); 16 break; 17 case R.styleable.Snow_maxSize: 18 mMaxSize = array.getInteger(attr, 70); 19 break; 20 case R.styleable.Snow_flakeSrc: 21 Integer srcId = array.getResourceId(attr, R.drawable.snow_flake); 22 mSnowBitmap = BitmapFactory.decodeResource(getResources(), srcId); 23 break; 24 case R.styleable.Snow_speedX: 25 mSpeedX = array.getInteger(attr, 10); 26 break; 27 case R.styleable.Snow_speedY: 28 mSpeedY = array.getInteger(attr, 10); 29 break; 30 default: 31 break; 32 } 33 } 34 if (mMinSize > mMaxSize) { 35 mMaxSize = mMinSize; 36 } 37 array.recycle(); 38 }
初始化 SurfaceHolder 部分:
1 private void initHolder() { 2 mHolder = this.getHolder(); 3 mHolder.setFormat(PixelFormat.TRANSLUCENT); 4 mHolder.addCallback(this); 5 }
(4)在Snow類中添加以下變量,並重寫 onMeasure() 函數,測量SurfaceView的大小:
1 @Override 2 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 3 //--- measure the view's width
4 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 5 if (widthMode == MeasureSpec.EXACTLY) { 6 mViewWidth = MeasureSpec.getSize(widthMeasureSpec); 7 } else { 8 mViewWidth = (getPaddingStart() + mSnowBitmap.getWidth() + getPaddingEnd()); 9 } 10
11 //--- measure the view's height
12 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 13 if (heightMode == MeasureSpec.EXACTLY) { 14 mViewHeight = MeasureSpec.getSize(heightMeasureSpec); 15 } else { 16 mViewHeight = (getPaddingTop() + mSnowBitmap.getHeight() + getPaddingBottom()); 17 } 18
19 setMeasuredDimension(mViewWidth, mViewHeight); 20 }
(5)初始化snow flakes的函數:經過隨機數生成必定範圍內的座標值和snow flake 的大小值,一開始時雪花是在屏幕上方的:
1 private void initSnowFlakes() { 2 mFlakes = new SnowFlake[mFlakeCount]; 3 boolean isRightDir = new Random().nextBoolean(); 4 for (int i = 0; i < mFlakes.length; i++) { 5 mFlakes[i] = new SnowFlake(); 6 mFlakes[i].setWidth(new Random().nextInt(mMaxSize-mMinSize) + mMinSize); 7 mFlakes[i].setHeight(mFlakes[i].getWidth()); 8 mFlakes[i].setX(new Random().nextInt(mViewWidth)); 9 mFlakes[i].setY(-(new Random().nextInt(mViewHeight))); 10 mFlakes[i].setSpeedY(new Random().nextInt(4) + mSpeedY); 11 if (isRightDir) { 12 mFlakes[i].setSpeedX(new Random().nextInt(4) + mSpeedX); 13 } 14 else { 15 mFlakes[i].setSpeedX(-(new Random().nextInt(4) + mSpeedX)); 16 } 17 } 18 }
(6)繪製snow flakes 的函數:經過SurfaceHolder 的lockCanvas()函數獲取到畫布,繪製完後再調用 unlockCanvasAndPost() 函數釋放canvas並將緩衝區繪製的內容一次性繪製到canvas上:
1 private void drawView() { 2 if (mHolder == null) { 3 return; 4 } 5 Canvas canvas = mHolder.lockCanvas(); 6 if (canvas == null) { 7 return; 8 } 9 canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); 10 drawSnow(canvas); 11 mHolder.unlockCanvasAndPost(canvas); 12 } 13
14 private void drawSnow(Canvas canvas) { 15 Rect rect = new Rect(); 16 Paint paint = new Paint(); 17 for (SnowFlake flake : mFlakes) { 18 rect.left = flake.getX(); 19 rect.top = flake.getY(); 20 rect.right = rect.left + flake.getWidth(); 21 rect.bottom = rect.top + flake.getHeight(); 22 canvas.drawBitmap(mSnowBitmap, null, rect, paint); 23 } 24 }
(7)更新snow flakes的參數的函數:
1 private void updatePara() { 2 int x; 3 int y; 4 for (SnowFlake flake : mFlakes) { 5 if (flake == null) { 6 break; 7 } 8 x = flake.getX() + flake.getSpeedX(); 9 y = flake.getY() + flake.getSpeedY(); 10 if ((x > mViewWidth + 20 || x < 0) 11 || (y > mViewHeight + 20)) { 12 x = new Random().nextInt(mViewWidth); 13 y = 0; 14 } 15 flake.setX(x); 16 flake.setY(y); 17 } 18 }
(8)開啓繪畫線程的 start 函數:
1 public void start() { 2 new Thread(){ 3 @Override 4 public void run() { 5 while (true) { 6 try { 7 if (mStart) { 8 updatePara(); 9 drawView(); 10 } 11 Thread.sleep(20); 12 } 13 catch (Exception ex) { 14 ex.printStackTrace(); 15 } 16 } 17 } 18 }.start(); 19 }
(9)修改 surfaceCreated(SurfaceHolder holder) 函數,即在SurfaceView建立完成後初始化snow flakes,並開啓動畫線程:
1 @Override 2 public void surfaceCreated(SurfaceHolder holder) { 3 initSnowFlakes(); 4 start(); 5 }
(10)重寫 onVisibilityChanged() 函數,在控件不可見時中止更新和繪製控件,避免CPU資源浪費:
1 @Override 2 protected void onVisibilityChanged(View changedView, int visibility) { 3 super.onVisibilityChanged(changedView, visibility); 4 mStart = (visibility == VISIBLE); 5 }
4. 控件的使用:
因爲咱們作了不少封裝工做,因此控件使用是很簡單的, 在佈局文件中添加並設置對應屬性便可:
1 <com.haoye.snow.Snow 2 android:layout_width="match_parent" 3 android:layout_height="match_parent" 4 myview:flakeCount="30" 5 myview:minSize="30" 6 myview:maxSize="70" 7 myview:speedX="5" 8 myview:speedY="10" 9 myview:flakeSrc="@drawable/snow_flake"/>
--------------------------
至此,自定義SurfaceView控件就完成了,固然咱們還能夠添加一些其餘的效果,好比讓隨機生成的雪花小片的多一些,適當調節雪花的亮度等,這樣能夠更好地模擬遠處下雪的情景,使景色具備深度。
另外在上面的代碼實現中,其實經過
mHolder.setFormat(PixelFormat.TRANSLUCENT);
setZOrderOnTop(true);
這兩行代碼,咱們已經將SurfaceView設置爲背景透明的模式,在每次繪製的時候,經過
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
這行代碼清理屏幕後再從新繪製;這使得咱們能夠在控件外添加背景圖片,而不須要每次都在控件中重繪。