天气渐热,来片雪花降降温——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

相关文章
相关标签/搜索