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

本文介绍如何使用SurfaceView在Android中开发游戏,重点讲解了两种绘图刷新策略:脏矩形刷新和覆盖刷新,并展示了如何通过这两种策略提高帧率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

http://www.devdiv.com/article-2336-1.html

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

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

2011-8-16 16:09:08 上传
下载附件 (61.45 KB)


2.png

2011-8-16 16:09:16 上传
下载附件 (41.61 KB)


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. public class Main extends Activity {

  13. @Override

  14. public void onCreate(Bundle savedInstanceState) {

  15. super.onCreate(savedInstanceState);

  16. setContentView(new MySurfaceView(this));

  17. }

  18. // 自定义的SurfaceView子类

  19. class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback {

  20. // 背景图

  21. private Bitmap BackgroundImage;

  22. // 问号图

  23. private Bitmap QuestionImage;

  24. SurfaceHolder Holder;

  25. public MySurfaceView(Context context) {

  26. super(context);

  27. BackgroundImage = BitmapFactory.decodeResource(getResources(),

  28. R.drawable.bg);

  29. QuestionImage = BitmapFactory.decodeResource(getResources(),

  30. R.drawable.question);

  31. Holder = this.getHolder();// 获取holder

  32. Holder.addCallback(this);

  33. }

  34. @Override

  35. public void surfaceChanged(SurfaceHolder holder, int format, int width,

  36. int height) {

  37. // TODO Auto-generated method stub

  38. }

  39. @Override

  40. public void surfaceCreated(SurfaceHolder holder) {

  41. // 启动自定义线程

  42. new Thread(new MyThread()).start();

  43. }

  44. @Override

  45. public void surfaceDestroyed(SurfaceHolder holder) {

  46. // TODO Auto-generated method stub

  47. }

  48. // 自定义线程类

  49. class MyThread implements Runnable {

  50. @Override

  51. public void run() {

  52. Canvas canvas = null;

  53. int rotate = 0;// 旋转角度变量

  54. while (true) {

  55. try {

  56. canvas = Holder.lockCanvas();// 获取画布

  57. Paint mPaint = new Paint();

  58. // 绘制背景

  59. canvas.drawBitmap(BackgroundImage, 0, 0, mPaint);

  60. // 创建矩阵以控制图片旋转和平移

  61. Matrix m = new Matrix();

  62. // 设置旋转角度

  63. m.postRotate((rotate += 48) % 360,

  64. QuestionImage.getWidth() / 2,

  65. QuestionImage.getHeight() / 2);

  66. // 设置左边距和上边距

  67. m.postTranslate(47, 47);

  68. // 绘制问号图

  69. canvas.drawBitmap(QuestionImage, m, mPaint);

  70. // 休眠以控制最大帧频为每秒约30帧

  71. Thread.sleep(33);

  72. } catch (Exception e) {

  73. } finally {

  74. Holder.unlockCanvasAndPost(canvas);// 解锁画布,提交画好的图像

  75. }

  76. }

  77. }

  78. }

  79. }

  80. }
复制代码

模拟器中的运行效果:
3.png

2011-8-16 16:13:38 上传
下载附件 (281.26 KB)



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

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

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

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

2011-8-16 16:10:54 上传
下载附件 (38.62 KB)



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

2011-8-16 16:13:38 上传
下载附件 (283.07 KB)



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

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

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

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

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

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

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

2011-8-16 16:20:14 上传
下载附件 (40.68 KB)



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

2011-8-16 16:21:56 上传
下载附件 (125.85 KB)



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

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

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

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

2011-8-16 16:16:55 上传
下载附件 (4.56 KB)



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

2011-8-16 16:17:28 上传
下载附件 (52.01 KB)



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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值