Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android編程入門 >> Android自定義SurfaceView實現雪花效果

Android自定義SurfaceView實現雪花效果

編輯: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/

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved