Android SurfaceView 绘图覆盖刷新及脏矩形刷新方法

农村四月闲人少,勤学苦攻把名扬。这篇文章主要讲述Android SurfaceView 绘图覆盖刷新及脏矩形刷新方法相关的知识,希望能为你提供帮助。
http://www.cnblogs.com/SkyD/archive/2010/11/08/1871423.html
Android SurfaceView 绘图覆盖刷新及脏矩形刷新方法SurfaceView在android中用作游戏开发是最适宜的,本文就将演示游戏开发中常用的两种绘图刷新策略在SurfaceView中的实现方法。
首先我们来看一下本例需要用到的两个素材图片:
 
 
bj.jpg就是一个渐变图,用作背景。
question.png是一个半透明的图像,我们希望将它放在上面,围绕其圆心不断旋转。
实现代码如下:
package  SkyD.SurfaceViewTest;
 
import  android.app.Activity;
import  android.content.Context;
import  android.graphics.Bitmap;
import  android.graphics.BitmapFactory;
import  android.graphics.Canvas;
import  android.graphics.Matrix;
import  android.graphics.Paint;
import  android.os.Bundle;
import  android.view.SurfaceHolder;
import  android.view.SurfaceView;
 
public  class  Main  extends  Activity {
 
        @Override
        public  void  onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(new  MySurfaceView(this));
        }
【Android SurfaceView 绘图覆盖刷新及脏矩形刷新方法】 
        //  自定义的SurfaceView子类
        class  MySurfaceView  extends  SurfaceView  implements  SurfaceHolder.Callback {
 
              //  背景图
              private  Bitmap  BackgroundImage;
              //  问号图
              private  Bitmap  QuestionImage;
 
              SurfaceHolder  Holder;
 
              public  MySurfaceView(Context context) {
                      super(context);
                      BackgroundImage  = BitmapFactory.decodeResource(getResources(),
                                    R.drawable.bg);
                      QuestionImage  = BitmapFactory.decodeResource(getResources(),
                                    R.drawable.question);
 
                      Holder  =  this.getHolder(); //  获取holder
                      Holder.addCallback(this);
              }
 
              @Override
              public  void  surfaceChanged(SurfaceHolder holder,  int  format,  int  width,
                            int  height) {
                      //  TODO  Auto-generated method stub
 
              }
 
              @Override
              public  void  surfaceCreated(SurfaceHolder holder) {
                      //  启动自定义线程
                      new  Thread(new  MyThread()).start();
              }
 
              @Override
              public  void  surfaceDestroyed(SurfaceHolder holder) {
                      //  TODO  Auto-generated method stub
 
              }
 
              //  自定义线程类
              class  MyThread  implements  Runnable {
 
                      @Override
                      public  void  run() {
                            Canvas canvas =  null;
                            int  rotate = 0; //  旋转角度变量
                            while  (true) {
                                    try  {
                                          canvas =  Holder.lockCanvas(); //  获取画布
                                          Paint mPaint =  new  Paint();
                                          //  绘制背景
                                          canvas.drawBitmap(BackgroundImage, 0, 0, mPaint);
                                          //  创建矩阵以控制图片旋转和平移
                                          Matrix m =  new  Matrix();
                                          //  设置旋转角度
                                          m.postRotate((rotate += 48) % 360,
                                                        QuestionImage.getWidth() / 2,
                                                        QuestionImage.getHeight() / 2);
                                          //  设置左边距和上边距
                                          m.postTranslate(47, 47);
                                          //  绘制问号图
                                          canvas.drawBitmap(QuestionImage, m, mPaint);
                                          //  休眠以控制最大帧频为每秒约30帧
                                          Thread.sleep(33);
                                    }  catch  (Exception e) {
                                    }  finally  {
                                          Holder.unlockCanvasAndPost(canvas); //  解锁画布,提交画好的图像
                                    }
                            }
                      }
 
              }
 
        }
}
模拟器中的运行效果:
 
(注:图中的问号图形是在不断旋转中的)
这看起来不错,但是有一个问题:我们在代码中设置的帧频最大值是每秒30帧,而实际运行时的帧频根据目测就能看出是到不了30帧的,这是因为程序在每一帧都要对整个画面进行重绘,过多的时间都被用作绘图处理,所以难以达到最大帧频。
 
脏矩形刷新接下来我们将采取脏矩形刷新的方法来优化性能,所谓脏矩形刷新,意为仅刷新有新变化的部分所在的矩形区域,而其他没用的部分就不去刷新,以此来减少资源浪费。
我们可以通过在获取Canvas画布时,为其指派一个参数来声明我们需要画布哪个局部,这样就可以只获得这个部分的控制权:
 
在这里为了便于观察,我将矩形区域设定为问号图形的1/4区域,也就是说在整个画面中我们仅仅更新问号图形的1/4大小那么点区域,其执行效果为:
 
可以看到,仅有那1/4区域在快速刷新,其他部分都是静止不动的了,现在的刷新帧频差不多已经能达到最大帧频了,我们的优化起作用了:)
不过别高兴的太早,实际上如果把刷新区域扩大到整个问号图形所在的矩形区域的话,你会发现优化作用变得微乎其微了,还是没法达到最大帧频的,因为更新区域增大了3倍,带来的资源消耗也就大幅增加。
 
覆盖刷新这种情况下就应当考虑结合覆盖刷新方法再进一步优化了。
试想一下,我们每次刷新时最大的消耗在哪?
没错,在背景图绘制上,这个绘制区域非常大,会消耗我们很多资源,但实际上背景图在此例中是从不变化的,也就是说我们浪费了很多资源在无用的地方。
那么可不可以只绘制一次背景,以后每次都只绘制会动的问号图形呢?
完全可以,尝试修改一下代码,再前面加一个帧计数器,然后我们仅在第一帧的时候绘制背景:
 
这样很简单,但是改后直接运行的话你会发现一个奇怪的状况:
 
问号图案会变得有残影了。
啊哈,这正是我使用半透明图案做范例的目的,通过这个重影,我们就能看出,覆盖刷新其实就是将每次的新的图形绘制到上一帧去,所以如果图像是半透明的,就要考虑重复叠加导致的问题了,而如果是完全不透明的图形则不会有任何问题。
背景会在背景图和黑色背景之间来回闪。
这个问题其实是源于SurfaceView的双缓冲机制,我理解就是它会缓冲前两帧的图像交替传递给后面的帧用作覆盖,这样由于我们仅在第一帧绘制了背景,第二帧就是无背景状态了,且通过双缓冲机制一直保持下来,解决办法就是改为在前两帧都进行背景绘制:
 
现在就没有问题了(如果换成个不透明的图形的话就真没问题了):
 
现在虽然还是达不到最大帧频,但是也算不错啦,在真机上跑的会更快些,接近最大帧频了。
 
结语我这也是刚接触Android开发,分享这点心得出来,有写的不对的欢迎指点一二^^
注意:此博客已停止更新,并迁移至blog.SkyDev.cc,后续都将在新地址更新。 

    推荐阅读