实现雪花的效果其实也能够经过自定义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);
这行代码清理屏幕后再从新绘制;这使得咱们能够在控件外添加背景图片,而不须要每次都在控件中重绘。