• 天气渐热,来片雪花降降温——Android自定义SurfaceView实现雪花效果


     

     

      实现雪花的效果其实也可以通过自定义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. 首先确定一片雪花所需要的参数:长、宽、在屏幕上的坐标、下落的水平/垂直速度....恩先这些吧,把它们封装到一个类里面:

     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

     

     

     

  • 相关阅读:
    思维导图形式带你读完《大型网站技术架构》中
    思维导图形式带你读完《大型网站技术架构》上
    淘淘商城项目补充(2)商品上架和下架功能实现
    淘淘商城项目补充(1)批量删除商品功能实现
    商城项目(ssm+dubbo+nginx+mysql统合项目)总结(3)
    阿里Java研发工程师实习面经,附面试技巧
    商城项目(ssm+dubbo+nginx+mysql统合项目)总结(2)
    商城项目(ssm+dubbo+nginx+mysql统合项目)总结(1)
    高性能优秀的服务框架-dubbo介绍
    回溯算法_01背包问题_Java实现
  • 原文地址:https://www.cnblogs.com/laishenghao/p/5396185.html
Copyright © 2020-2023  润新知