Android SurfaceView 绘图覆盖刷新及“.NET研究”脏矩形刷新方法

简介:   SurfaceView在Android中用作游戏开发是最适宜的,本文就将演示游戏开发中常用的两种绘图刷新策略在SurfaceView中的实现方法。  首先我们来看一下本例需要用到的两个素材图片:  bj.jpg就是一个渐变图,用作背景。

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

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

imageimage  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 ));
}

// 自定义的SurfaceView子类
class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback {

// 背景图
private Bitmap BackgroundImage;
// 问号图
private Bitmap QuestionImage;

SurfaceHolder Holder;

public MySurfaceView(Context context) {
super (context);
BackgroundImage
= BitmapFactory.decodeResou上海企业网站制作rce(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);
// 解锁画布,提交画好的图像
}
}
}
}
}
}

  模拟器中的运行效果:

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

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

  脏矩形刷新

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

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

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

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

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

  覆盖刷新

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

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

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

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

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

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

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

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

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

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

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

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

  结语

  我这也是刚接触Android开发,分享这点心得出来,有写的不对的欢迎指点一二^^

目录
相关文章
|
7月前
|
Android开发
Android Launcher研究(二)-----------Launcher为何物,究竟是干什么
Android Launcher研究(二)-----------Launcher为何物,究竟是干什么
244 2
|
3月前
|
安全 Android开发 Kotlin
Android经典实战之SurfaceView原理和实践
本文介绍了 `SurfaceView` 这一强大的 UI 组件,尤其适合高性能绘制任务,如视频播放和游戏。文章详细讲解了 `SurfaceView` 的原理、与 `Surface` 类的关系及其实现示例,并强调了使用时需注意的线程安全、生命周期管理和性能优化等问题。
187 8
|
2月前
|
安全 Java 网络安全
Android远程连接和登录FTPS服务代码(commons.net库)
Android远程连接和登录FTPS服务代码(commons.net库)
34 1
|
3月前
|
Android开发 开发者 索引
Android实战经验之如何使用DiffUtil提升RecyclerView的刷新性能
本文介绍如何使用 `DiffUtil` 实现 `RecyclerView` 数据集的高效更新,避免不必要的全局刷新,尤其适用于处理大量数据场景。通过定义 `DiffUtil.Callback`、计算差异并应用到适配器,可以显著提升性能。同时,文章还列举了常见错误及原因,帮助开发者避免陷阱。
278 9
|
3月前
|
Android开发
Android中SurfaceView的双缓冲机制和普通View叠加问题解决办法
本文介绍了 Android 平台上的 SurfaceView,这是一种高效的图形渲染控件,尤其适用于视频播放、游戏和图形动画等场景。文章详细解释了其双缓冲机制,该机制通过前后缓冲区交换来减少图像闪烁,提升视觉体验。然而,SurfaceView 与普通 View 叠加时可能存在 Z-Order 不一致、同步问题及混合渲染难题。文中提供了使用 TextureView、调整 Z-Order 和创建自定义组合控件等多种解决方案。
173 9
|
4月前
|
安全 Java 网络安全
Android远程连接和登录FTPS服务代码(commons.net库)
很多文章都介绍了FTPClient如何连接ftp服务器,但却很少有人说如何连接一台开了SSL认证的ftp服务器,现在代码来了。
118 2
|
5月前
|
Android开发 开发者
Android经典面试题之SurfaceView和TextureView有什么区别?
分享了`SurfaceView`和`TextureView`在Android中的角色。`SurfaceView`适于视频/游戏,独立窗口低延迟,但变换受限;`TextureView`支持复杂变换,视图层级中渲染,适合动画/视频特效,但性能略低。两者在性能、变换、使用和层级上有差异,开发者需按需选择。
104 1
|
4月前
分享一份 .NET Core 简单的自带日志系统配置,平时做一些测试或个人代码研究,用它就可以了
分享一份 .NET Core 简单的自带日志系统配置,平时做一些测试或个人代码研究,用它就可以了
|
4月前
|
Android开发
解决Android、Flutter编译时Gradle报错:javax.net.ssl.SSLException: Connection reset
解决Android、Flutter编译时Gradle报错:javax.net.ssl.SSLException: Connection reset
499 0
|
6月前
|
安全 JavaScript 前端开发
kotlin开发安卓app,JetPack Compose框架,给webview新增一个按钮,点击刷新网页
在Kotlin中开发Android应用,使用Jetpack Compose框架时,可以通过添加一个按钮到TopAppBar来实现WebView页面的刷新功能。按钮位于右上角,点击后调用`webViewState?.reload()`来刷新网页内容。以下是代码摘要: