SurfaceView和View区别总结

一、View简介

View一般在onDraw方法里面绘图,onDrawUI主线程执行。onDraw默认只在View初始化的时候调用一遍,所以View不会自动刷新画面,一般要调用invalidate或者postInvalidate来重新执行onDraw里面的代码进行刷新画面。UI主线程一般用来渲染组件、处理组件与用户之间的交互事件,比如说按钮的点击事件、文本框的输入事件。如果你的画图任务相当繁重,那么onDraw方法里面的代码要执行好长一段时间,就可能会造成UI主线程阻塞。比如,如果在绘图的同时,用户刚好点击了某个按钮,那UI主线程会怎么处理点击事件呢?肯定是在绘图完毕后再处理点击事件,因为onDraw方法跟按钮点击事件处理都是在UI主线程内操作的。这样就给用户造成按钮点击延时处理的坏体验。

绘图原理:


 

主线程绘图代码和效果:

protected void onDraw(Canvas canvas) {
// 初始化画笔
Paint paint = new Paint();
// 设置抗锯齿, 文字清晰点, 不过相对比较耗性能
paint.setAntiAlias(true);
// 设置画笔颜色为红色
paint.setColor(Color.RED);
// 设置文字大小
paint.setTextSize(50);
// 画文字, 所画的内容都会缓存到Surface中
canvas.drawText("ITCAST", 100, 100, paint);
}

 
//既然在主线程内绘图可能会造成线程阻塞,那我们可不可以在onDraw里面开启一个新线程进行绘图呢?
//在后台线程绘图代码和效果:
protected void onDraw(final Canvas canvas) {
new Thread(){
public void run() {
// 初始化画笔
Paint paint = new Paint();
// 设置抗锯齿, 文字清晰点, 不过相对比较耗性能
paint.setAntiAlias(true);
// 设置画笔颜色为红色
paint.setColor(Color.RED);
// 设置文字大小
paint.setTextSize(50);
// 画文字, 所画的内容都会缓存到Surface中
canvas.drawText("ITCAST", 100, 100, paint);
}
}.start();
}
绘图效果:


 

可以发现屏幕上并没有显示任何文字,说明绘图不成功,因为android只允许在UI主线程内改变UI界面,绘图完毕后渲染到屏幕上的实质就是改变UI界面。

 

二、 SurfaceView简介

SurfaceView也可以在onDraw方法里面绘图,即直接在UI主线程绘图并渲染,因为SurfaceViewView的子类。上面已经介绍,绘图完毕后只能在UI主线程内渲染到屏幕上。为了不阻塞主线程,我们可以考虑采取这样的方案:在后台线程执行繁重的绘图任务,把所绘制的东西缓存起来;绘图完毕后,再回到UI主线程,一次性把所绘制的东西渲染到屏幕上。(本质就是后台线程绘图,UI主线程渲染)

只使用ViewonDraw方法是无法实现这种方案的,而SurfaceView可以实现这种方案。

先看看SurfaceView的工作原理图:


 

首先,可以确定的2点:1.使用Canvas对象进行绘图2.一定是在主线内渲染屏幕。由图可以看出,Canvas在后台线程绘制的东西先缓存到Surface中,然后让Surface回到UI主线程渲染到屏幕上。因此,要有Surface对象存在,才能够在屏幕上显示东西。顺着下面的问题来了解SurfaceView的整个工作原理。

1Surface是什么?

SurfaceSurfaceView里面的一个成员变量,它的创建和销毁过程都是自动的。Surface里面有个Canvas成员变量,将来我们就是要得到这个Canvas对象进行绘图。不过只能通过SurfaceHolder对象来控制和访问SurfaceSurface的主要作用是缓存Canvas绘制的东西,并渲染到屏幕上。

2SurfaceHolder对象怎么获取?

使用SurfaceViewgetHolder()

3)在什么时候开启后台绘图线程?

最好在Surface被创建的时候,开启绘图线程。因为没有Surface,绘制再多的东西也无法渲染到屏幕上。

4)在什么时候销毁后台绘图线程?

最好在Surface被销毁的时候,销毁绘图线程。因为没有Surface,绘制再多的东西也无法渲染到屏幕上。

5) 既然要在创建Surface时开启绘图线程,在销毁Surface时销毁绘图线程。那我们怎么知道Surface在什么时候创建,什么时候销毁呢?

那就需要监听Surface的生命周期,使用SurfaceHolder对象的addCallback()来添加Surface的生命周期监听器——SurfaceHolder.Callback

Callback里面对应的方法

//Surface的大小发生改变时调用

public void surfaceChanged(SurfaceHolder holder,int format,int width,int height){}

//Surface创建时调用,一般在这里开启绘图线程

public void surfaceCreated(SurfaceHolder holder){}

//Surface销毁时调用,一般在这里销毁绘图线程

public void surfaceDestroyed(SurfaceHolder holder) {}

 

再来看看如果搭建SurfaceView的游戏开发框架

1> 新建一个继承SurfaceView的类——GameView

 

2> 得到SurfaceHolder对象,并监听Surface的生命周期

SurfaceHolder holder;
public GameView(Context context) {
super(context);
// 获取SurfaceHolder对象
holder = getHolder();
// 监听Surface的生命周期
holder.addCallback(this);
}


可以看到addCallback传的参数是this,所以GameView必须实现Callback接口

public class GameView extends SurfaceView implements Callback

实现相应的方法

/**
 * Surface的大小发生改变时调用
 */
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
/**
 * Surface创建时调用,一般在这里开启绘图线程
 */
public void surfaceCreated(SurfaceHolder holder) {}
/**
 * Surface销毁时调用,一般在这里销毁绘图线程
 */
public void surfaceDestroyed(SurfaceHolder holder) {}


 

3> 开启绘图线程、销毁绘图线程

先保留一个线程成员变量

/**
* 绘图线程
*/
Thread thread;
/**
 * Surface的大小发生改变时调用
 */
public void surfaceChanged(SurfaceHolder holder, int format, int width,int height) {}
/**
 * Surface创建时调用,一般在这里开启绘图线程
 */
public void surfaceCreated(SurfaceHolder holder) {
// 开启绘图线程
thread = new Thread(this);
thread.start();
}
/**
 * Surface销毁时调用,一般在这里销毁绘图线程
 */
public void surfaceDestroyed(SurfaceHolder holder) {
if (thread != null && thread.isAlive()) {
try {
// 销毁绘图线程
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Thread里面传的是this,所以GameView必须实现Runnable接口
public class GameView extends SurfaceView implements Callback, Runnable
实现run方法
/**
 * 这是后台绘图线程, 在这里得到Surface中Canvas进行绘图
 */
public void run() {}


 

4> run方法中尝试进行绘图

public void run() {
// 通过Holder对象获得Surface的Canvas成员变量
Canvas canvas = holder.lockCanvas();
// 初始化画笔
Paint paint = new Paint();
// 设置抗锯齿, 文字清晰点, 不过相对比较耗性能
paint.setAntiAlias(true);
// 设置画笔颜色为红色
paint.setColor(Color.RED);
// 设置文字大小
paint.setTextSize(50);
// 画文字, 所画的内容都会缓存到Surface中
canvas.drawText("ITCAST", 100, 100, paint);
// 画完之后, 再将缓存的东西渲染到屏幕上
holder.unlockCanvasAndPost(canvas);
}


效果:

 

虽然可以画东西了,但是我们都知道,这个run方法只执行一遍,画一次就没了。我们游戏中一般都是不断地刷新屏幕,所以我们应该循环调用那段绘图代码进行刷新屏幕。我们尝试让文字动起来,就改变它的y值吧。

 

5> 动态刷新屏幕

先搞个循环标记

/**
 * 是否正在绘图
 */
boolean running;
在开启绘图线程前改变循环标记
/**
 * Surface创建时调用,一般在这里开启绘图线程
 */
public void surfaceCreated(SurfaceHolder holder) {
// 开启绘图线程
running = true;
thread = new Thread(this);
thread.start();
}
/**
 * Surface销毁时调用,一般在这里销毁绘图线程
 */
public void surfaceDestroyed(SurfaceHolder holder) {
// 改变循环标记
running = false;
if (thread != null && thread.isAlive()) {
try {
// 销毁绘图线程
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}


接下来循环绘图,让文字从上到下移动

int y = 0;
public void run() {
   while (running) {
// 通过Holder对象获得Surface的Canvas成员变量
Canvas canvas = holder.lockCanvas();
// 先清理屏幕
canvas.drawColor(Color.BLACK);
// 初始化画笔
Paint paint = new Paint();
// 设置抗锯齿, 文字清晰点, 不过相对比较耗性能
paint.setAntiAlias(true);
// 设置画笔颜色为红色
paint.setColor(Color.RED);
// 设置文字大小
paint.setTextSize(50);
// 画文字, 所画的内容都会缓存到Surface中
canvas.drawText("ITCAST", 100, y++, paint);
// 画完之后, 再将缓存的东西渲染到屏幕上
holder.unlockCanvasAndPost(canvas);
}
}


这里需要注意的是,每次绘图前都要先清理屏幕canvas.drawColor(Color.BLACK);

.

6> 改善下代码

public void run() {
// 把Canvas放在外面提高性能,不必每次都定义这个变量
Canvas canvas = null;
// 将成员变量转为局部变量,也是提高性能
SurfaceHolder holder = this.holder;
while(running) {
// 由于涉及到多线程,这个对象最好同步
synchronized (holder) {
// 画完后最好一定释放Canvas,渲染屏幕,避免内存泄露,所以用try-finally
try {
// 通过Holder对象获得Surface的Canvas成员变量
canvas = holder.lockCanvas();
// 在这里进行绘图
render(canvas);
// 画完之后可以适当暂停,没必要持续画,人的眼睛有一定的反应时间
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 严谨起见,最好判断canvas是否为null,不然unlockCanvasAndPost(null)会抛异常
if (canvas != null) {
// 画完之后, 再将缓存的东西渲染到屏幕上
holder.unlockCanvasAndPost(canvas);
}
}
}
}
}
int y = 0;
/**
 * 绘图逻辑
 */
public void render(Canvas canvas) {
// 先清理屏幕
canvas.drawColor(Color.BLACK);
// 初始化画笔
Paint paint = new Paint();
// 设置抗锯齿, 文字清晰点, 不过相对比较耗性能
paint.setAntiAlias(true);
// 设置画笔颜色为红色
paint.setColor(Color.RED);
// 设置文字大小
paint.setTextSize(50);
// 画文字, 所画的内容都会缓存到Surface中
canvas.drawText("ITCAST", 100, y++, paint);
}


 

这就是整个SurfaceView的游戏框架

 

三、ViewSurfaceView对比

View:必须在UI的主线程中更新画面,用于被动更新画面

surfaceViewUI线程和子线程中都可以。在一个新启动的线程中重新绘制画面,主动更新画面。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值