在SurfaceView中实现覆盖刷新和脏矩形刷的方法

 SurfaceView在Android中用作游戏开发是最适宜的,本文就将演示游戏开发中常用的两种绘图刷新策略在SurfaceView中的实现方法。

  首先我们来看一下本例需要用到的两个素材图片:

imageimage  bj.jpg就是一个渐变图,用作背景。

  question.png是一个半透明的图像,我们希望将它放在上面,围绕其圆心不断旋转。

  实现代码如下:

 
 
  1. package SkyD.SurfaceViewTest;
  2. import android.app.Activity;
  3. import android.content.Context;
  4. import android.graphics.Bitmap;
  5. import android.graphics.BitmapFactory;
  6. import android.graphics.Canvas;
  7. import android.graphics.Matrix;
  8. import android.graphics.Paint;
  9. import android.os.Bundle;
  10. import android.view.SurfaceHolder;
  11. import android.view.SurfaceView;
  12.   
  13. public class Main extends Activity {
  14.   
  15.     @Override
  16.     public void onCreate(Bundle savedInstanceState) {
  17.        super.onCreate(savedInstanceState);
  18.        setContentView(new MySurfaceView(this));
  19.     }
  20.   
  21.     // 自定义的SurfaceView子类
  22.     class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback {
  23.   
  24.        // 背景图
  25.        private Bitmap BackgroundImage;
  26.        // 问号图
  27.        private Bitmap QuestionImage;
  28.   
  29.        SurfaceHolder Holder;
  30.   
  31.        public MySurfaceView(Context context) {
  32.            super(context);
  33.            BackgroundImage = BitmapFactory.decodeResource(getResources(),
  34.                   R.drawable.bg);
  35.            QuestionImage = BitmapFactory.decodeResource(getResources(),
  36.                   R.drawable.question);
  37.   
  38.            Holder = this.getHolder();// 获取holder
  39.            Holder.addCallback(this);
  40.        }
  41.   
  42.        @Override
  43.        public void surfaceChanged(SurfaceHolder holder, int format, int width,
  44.               int height) {
  45.            // TODO Auto-generated method stub
  46.   
  47.        }
  48.   
  49.        @Override
  50.        public void surfaceCreated(SurfaceHolder holder) {
  51.            // 启动自定义线程
  52.            new Thread(new MyThread()).start();
  53.        }
  54.   
  55.        @Override
  56.        public void surfaceDestroyed(SurfaceHolder holder) {
  57.           // TODO Auto-generated method stub
  58.        }
  59.        // 自定义线程类
  60.        class MyThread implements Runnable {
  61.            @Override
  62.            public void run() {
  63.               Canvas canvas = null;
  64.               int rotate = 0;// 旋转角度变量
  65.               while (true) {
  66.                   try {
  67.                      canvas = Holder.lockCanvas();// 获取画布
  68.                      Paint mPaint = new Paint();
  69.                      // 绘制背景
  70.                      canvas.drawBitmap(BackgroundImage, 0, 0, mPaint);
  71.                      // 创建矩阵以控制图片旋转和平移
  72.                      Matrix m = new Matrix();
  73.                      // 设置旋转角度
  74.                      m.postRotate((rotate += 48) % 360,
  75.                             QuestionImage.getWidth() / 2,
  76.                             QuestionImage.getHeight() / 2);
  77.                      // 设置左边距和上边距
  78.                      m.postTranslate(47, 47);
  79.                      // 绘制问号图
  80.                      canvas.drawBitmap(QuestionImage, m, mPaint);
  81.                      // 休眠以控制最大帧频为每秒约30帧
  82.                      Thread.sleep(33);
  83.                   } catch (Exception e) {
  84.                   } finally {
  85.                      Holder.unlockCanvasAndPost(canvas);// 解锁画布,提交画好的图像
  86.                   }
  87.              }
  88.           }
  89.        }
  90.     }
  91. }

  模拟器中的运行效果:

image  (注:图中的问号图形是在不断旋转中的)

  这看起来不错,但是有一个问题:我们在代码中设置的帧频最大值是每秒30帧,而实际运行时的帧频根据目测就能看出是到不了30帧的,这是因为程序在每一帧都要对整个画面进行重绘,过多的时间都被用作绘图处理,所以难以达到最大帧频。

  脏矩形刷新

  接下来我们将采取脏矩形刷新的方法来优化性能,所谓脏矩形刷新,意为仅刷新有新变化的部分所在的矩形区域,而其他没用的部分就不去刷新,以此来减少资源浪费。

  我们可以通过在获取Canvas画布时,为其指派一个参数来声明我们需要画布哪个局部,这样就可以只获得这个部分的控制权:

image  在这里为了便于观察,我将矩形区域设定为问号图形的1/4区域,也就是说在整个画面中我们仅仅更新问号图形的1/4大小那么点区域,其执行效果为:

SNAGHTML342f602  可以看到,仅有那1/4区域在快速刷新,其他部分都是静止不动的了,现在的刷新帧频差不多已经能达到最大帧频了,我们的优化起作用了:)

  不过别高兴的太早,实际上如果把刷新区域扩大到整个问号图形所在的矩形区域的话,你会发现优化作用变得微乎其微了,还是没法达到最大帧频的,因为更新区域增大了3倍,带来的资源消耗也就大幅增加。

  覆盖刷新

  这种情况下就应当考虑结合覆盖刷新方法再进一步优化了。

  试想一下,我们每次刷新时最大的消耗在哪?

  没错,在背景图绘制上,这个绘制区域非常大,会消耗我们很多资源,但实际上背景图在此例中是从不变化的,也就是说我们浪费了很多资源在无用的地方。

  那么可不可以只绘制一次背景,以后每次都只绘制会动的问号图形呢?

  完全可以,尝试修改一下代码,再前面加一个帧计数器,然后我们仅在第一帧的时候绘制背景:

image  这样很简单,但是改后直接运行的话你会发现一个奇怪的状况:

image  问号图案会变得有残影了。

  啊哈,这正是我使用半透明图案做范例的目的,通过这个重影,我们就能看出,覆盖刷新其实就是将每次的新的图形绘制到上一帧去,所以如果图像是半透明的,就要考虑重复叠加导致的问题了,而如果是完全不透明的图形则不会有任何问题。

  背景会在背景图和黑色背景之间来回闪。

  这个问题其实是源于SurfaceView的双缓冲机制,我理解就是它会缓冲前两帧的图像交替传递给后面的帧用作覆盖,这样由于我们仅在第一帧绘制了背景,第二帧就是无背景状态了,且通过双缓冲机制一直保持下来,解决办法就是改为在前两帧都进行背景绘制:

image  现在就没有问题了(如果换成个不透明的图形的话就真没问题了):

image  现在虽然还是达不到最大帧频,但是也算不错啦,在真机上跑的会更快些,接近最大帧频了。


SurfaceView播放视频的实现步骤如下: 1. 创建SurfaceView对象和MediaPlayer对象。 ```java SurfaceView surfaceView = findViewById(R.id.surface_view); MediaPlayer mediaPlayer = new MediaPlayer(); ``` 2. 设置SurfaceHolder.Callback回调,监听SurfaceView的状态变化。当SurfaceView的状态变为就绪状态时,可以开始播放视频。 ```java surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() { @Override public void surfaceCreated(SurfaceHolder holder) { // SurfaceView已经创建完成,可以进行视频播放 mediaPlayer.setDisplay(holder); mediaPlayer.setDataSource("视频文件路径"); mediaPlayer.prepareAsync(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // SurfaceView发生变化,可以在这里进行一些UI操作 } @Override public void surfaceDestroyed(SurfaceHolder holder) { // SurfaceView被销毁,可以在这里释放资源 mediaPlayer.release(); } }); ``` 3. 设置MediaPlayer的监听器,监听视频播放的状态变化。 ```java mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { @Override public void onPrepared(MediaPlayer mp) { // 视频准备完成,可以开始播放 mediaPlayer.start(); } }); mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { // 视频播放完成,可以进行一些UI操作 } }); mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() { @Override public boolean onError(MediaPlayer mp, int what, int extra) { // 视频播放出错,可以进行一些错误处理 return false; } }); ``` 4. 在Activity的生命周期方法,控制MediaPlayer的状态变化。 ```java @Override protected void onResume() { super.onResume(); mediaPlayer.start(); } @Override protected void onPause() { super.onPause(); mediaPlayer.pause(); } @Override protected void onDestroy() { super.onDestroy(); mediaPlayer.release(); } ``` 以上就是在SurfaceView播放视频的基本实现方法,可根据具体需求进行调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值