天氣漸熱,來片雪花降降溫——Android自定義SurfaceView實現雪花效果

 

 

  實現雪花的效果其實也能夠經過自定義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);

這行代碼清理屏幕後再從新繪製;這使得咱們能夠在控件外添加背景圖片,而不須要每次都在控件中重繪。

 

 

 源碼下載:https://github.com/laishenghao/Snow/

 轉載請註明:http://www.cnblogs.com/laishenghao/p/5396185.html

相關文章
相關標籤/搜索