在做android开发时需要自定义组件,在网上查资料时看到有些例子 extends SurfaceView
,这篇文章主要讲解SurfaceView和View的区别,已经对SurfaceView的理解和用法。
先来讲解一下Surface.
Surface
简单的说Surface对应了一块屏幕缓冲区,每个window对应一个Surface,任何View都要画在Surface的Canvas上(后面有原因解释)。传统的view共享一块屏幕缓冲区,所有的绘制必须在UI线程中进行。
在SDK的文档中,对Surface的描述是这样的:“Handle onto a raw buffer that is being managed by the screen compositor”,翻译成中文就是“由屏幕显示内容合成器(screen compositor)所管理的原始缓冲区的句柄”,这句话包括下面两个意思:
- 1、通过Surface(因为Surface是句柄)就可以获得原生缓冲器以及其中的内容。就像在C++语言中,可以通过一个文件的句柄,就可以获得文件的内容一样。
- 2、 原始缓冲区(a raw buffer)是用于保存当前窗口的像素数据的。
引伸地,可以认为Android中的Surface就是一个用来画图形(graphics)或图像(image)的地方。
根据Java方面的常规知识,我们知道通常画图是在一个Canvas对象上面进行的,由此,可以推知一个Surface对象中应该包含有一个Canvas(画布)对象。因此,在前面提及的两个意思的基础上,可以再加上一条:
- 3、Surface中有一个Canvas成员,专门用于画图的。
由以上的概括,我们总结如下:Surface中的Canvas成员,是专门用于供程序员画图的场所,就像黑板一样;其中的原始缓冲区是用来保存数据的地方;Surface本身的作用类似一个句柄,得到了这个句柄就可以得到其中的Canvas、原始缓冲区以及其它方面的内容。
Surface是用来管理数据的。(句柄)
SurfaceView
说SurfaceView是一个View也许不够严谨,然而从定义中pubilc classSurfaceView extends View{…..}显示SurfaceView确实是派生自View,但是SurfaceView却有自己的Surface,请看SurfaceView的源码:
if (mWindow == null) {
mWindow = new MyWindow(this);
mLayout.type = mWindowType;
mLayout.gravity = Gravity.LEFT|Gravity.TOP;
mSession.addWithoutInputChannel(mWindow, mWindow.mSeq, mLayout,
mVisible ? VISIBLE : GONE, mContentInsets);
}
很明显,每个SurfaceView创建的时候都会创建一个MyWindow,new MyWindow(this)中的this正是SurfaceView自身,因此将SurfaceView和window绑定在一起,由第一部分我们知道,一个window对应一个Surface,因此SurfaceView也就内嵌了一个自己的Surface,可以认为SurfaceView是用来控制Surface中View的位置和尺寸的。
SurfaceView就是展示Surface中数据的地方,同时可以认为SurfaceView是用来控制Surface中View的位置和尺寸的。
View与SurfaceView的区别
大家都知道,传统View及其派生类的更新只能在UI线程,然而UI线程还同时处理其他交互逻辑,这就无法保证View更新的速度和帧率了,而SurfaceView可以用独立的线程进行绘制,因此可以提供更高的帧率,例如游戏,摄像头取景等场景就比较适合SurfaceView来实现。
当使用surfaceView 由于是在新的线程中更新画面所以不会阻塞你的UI主线程。但这也带来了另外一个问题,就是事件同步。比如你触屏了一下,你需要surfaceView中thread处理,一般就需要有一个event queue的设计来保存touch event,这会稍稍复杂一点,因为涉及到线程同步。
所以基于以上,根据游戏特点,一般分成两类。
1 被动更新画面的。比如棋类,这种用view就好了。因为画面的更新是依赖于 onTouch 来更新,可以直接使用 invalidate。 因为这种情况下,这一次Touch和下一次的Touch需要的时间比较长些,不会产生影响。
2 主动更新。比如一个人在一直跑动。这就需要一个单独的thread不停的重绘人的状态,避免阻塞main UI thread。所以显然view不合适,需要surfaceView来控制。
SurfaceHolder
SurfaceHolder是一个接口,其作用就像一个关于Surface的监听器,提供访问和控制SurfaceView内嵌的Surface 相关的方法。它通过三个回调方法,让我们可以感知到Surface的创建、销毁或者改变。
在SurfaceView中有一个方法getHolder,可以很方便地获得SurfaceView内嵌的Surface所对应的监听器接口SurfaceHolder(有点拗口吧)。
除下面将要提到的SurfaceHolder.Callback外,SurfaceHolder还提供了很多重要的方法,其中最重要的就是:
- 1、abstract void
addCallback(SurfaceHolder.Callbackcallback):为SurfaceHolder添加一个SurfaceHolder.Callback回调接口。 - 2、abstract Canvas
lockCanvas():获取一个Canvas对象,并锁定之。所得到的Canvas对象,其实就是Surface中一个成员。 - 3、abstract Canvas lockCanvas(Rect
dirty):同上。但只锁定dirty所指定的矩形区域,因此效率更高。 - 4、abstract void unlockCanvasAndPost(Canvas
canvas):当修改Surface中的数据完成后,释放同步锁,并提交改变,然后将新的数据进行展示,同时Surface中相关数据会被丢失。
2、3、4中的同步锁机制的目的,就是为了在绘制的过程中,Surface中的数据不会被改变。lockCanvas是为了防止同一时刻多个线程对同一canvas写入。
通过SurfaceView的getHolder()函数可以获取SurfaceHolder对象,Surface 就在SurfaceHolder对象内。虽然Surface保存了当前窗口的像素数据,但是在使用过程中是不直接和Surface打交道的,由SurfaceHolder的Canvas lockCanvas()或则Canvas lockCanvas(Rect dirty)函数来获取Canvas对象,通过在Canvas上绘制内容来修改Surface中的数据。如果Surface不可编辑或则尚未创建调用该函数会返回null,在 unlockCanvas() 和 lockCanvas()中Surface的内容是不缓存的,所以需要完全重绘Surface的内容,为了提高效率只重绘变化的部分则可以调用lockCanvas(Rect dirty)函数来指定一个dirty区域,这样该区域外的内容会缓存起来。在调用lockCanvas函数获取Canvas后,SurfaceView会获取Surface的一个同步锁直到调用unlockCanvasAndPost(Canvas canvas)函数才释放该锁,这里的同步机制保证在Surface绘制过程中不会被改变(被摧毁、修改)。
为了充分利用不同平台的资源,发挥平台的最优效果可以通过SurfaceHolder的setType函数来设置绘制的类型,目前接收如下的参数:
- SURFACE_TYPE_NORMAL:用RAM缓存原生数据的普通Surface
- SURFACE_TYPE_HARDWARE:适用于DMA(Direct memory access )引擎和硬件加速的Surface
- SURFACE_TYPE_GPU:适用于GPU加速的Surface
- SURFACE_TYPE_PUSH_BUFFERS:表明该Surface不包含原生数据,Surface用到的数据由其他对象提供,在Camera图像预览中就使用该类型的Surface,有Camera负责提供给预览Surface数据,这样图像预览会比较流畅。如果设置这种类型则就不能调用lockCanvas来获取Canvas对象了。
总结:从设计模式的高度来看,Surface、SurfaceView和SurfaceHolder实质上就是广为人知的MVC,即Model-View-Controller。Model就是模型的意思,或者说是数据模型,或者更简单地说就是数据,也就是这里的Surface;View即视图,代表用户交互界面,也就是这里的SurfaceView;SurfaceHolder很明显可以理解为MVC中的Controller(控制器)。
SurfaceHolder.Callback
使用的SurfaceView的时候,一般情况下还要对其进行创建,销毁,改变时的情况进行监视,这就要用到SurfaceHolder.Callback. SurfaceHolder.Callback在底层的Surface状态发生变化的时候通知View. SurfaceHolder.Callback主要是当底层的Surface被创建、销毁或者改变时提供回调通知,由于绘制必须在Surface被创建后才能进行,因此SurfaceHolder.Callback中的surfaceCreated 和surfaceDestroyed 就成了绘图处理代码的边界。
SurfaceHolder.Callback中定义了三个接口方法:
- 1、abstract void surfaceChanged(SurfaceHolder holder, int format, int
width, int height):当surface发生任何结构性的变化时(格式或者大小),该方法就会被立即调用。 - 2、abstract void surfaceCreated(SurfaceHolder
holder):当surface对象创建后,该方法就会被立即调用。 - 3、abstract void surfaceDestroyed(SurfaceHolder
holder):当surface对象在将要销毁前,该方法会被立即调用。
使用SurfaceView需要注意以下几点情况: SurfaceView和SurfaceHolder.Callback函数都从当前SurfaceView窗口线程中调用(一般而言就是程序的主线程)
注意
注意:
- 1.所有SurfaceView和SurfaceHolder.Callback的方法都应该在UI线程里调用,一般来说就是应用程序主线程。 渲染线程所要访问的各种变量应该作同步处理。
- 2.由于surface可能被销毁,它只在SurfaceHolder.Callback.surfaceCreated()和 SurfaceHolder.Callback.surfaceDestroyed()之间有效,所以要确保渲染线程访问的是合法有效的surface。可以在主线程之外的线程中向屏幕绘图,这样可以避免画图任务繁重的时候造成主线程阻塞,从而提高了程序的反应速度。
在游戏开发中多用到SurfaceView,游戏中的背景、人物、动画等等尽量在画布canvas中画出。
- 3.View是没有缓存机制的,每次绘图都是重新绘制 若要使用双缓冲机制的话。
1、自定义一个View 实现onDraw方法
2、调用这个方法,调用刷新onDraw()方法ssinView.postInvalidate();
3.需要格外创建一个Canvas,每次都
public void onDraw(Canvas canvas){
if(x >= 480){
x = 0; //清空画布
canvas.drawColor(Color.BLACK); //重新创建一个新的图
bitmap =Bitmap.createBitmap(480, 104, Config.ARGB_8888);
}
else{
//在缓冲中将之前的图绘制好,然后在真正的Canvas上绘制缓冲中的图
Canvas c = new Canvas();
c.setBitmap(bitmap);
int y = (int)(A*Math.sin(x/180.0f*Math.PI));
System.out.println("==================y:"+y+"===x:"+x);
c.drawPoint(x, y+A, paint); //canvas.save();
canvas.drawBitmap(bitmap, 0, 0,paint); x+=5;
}
}
- 4..Surfaceview实现了双缓冲技术
提供了一个类SurfaceHolder
1、继承SurfaceView 实现Callback接口(在new SurfaceView的时候会自动回调)
2、刷新SurfaceView
// TODO Auto-generated method stub
super.run();
while(flag == true){
//锁定,获得Canvas
Canvas canvas = surfaceHolder.lockCanvas(null);
onDraw(canvas); //释放锁
surfaceHolder.unlockCanvasAndPost(canvas);
asinSurfaceView.x += 5;
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e){
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Surfaceview控制Surface的绘制位置。
- 第一步:继承SurfaceView并实现SurfaceHolder.Callback接口
- 第二步: SurfaceView.getHolder()获得SurfaceHolder对象
- 第三步:SurfaceHolder.addCallback(callback)添加回调函数
- 第四步:SurfaceHolder.lockCanvas()获得Canvas对象并锁定画布
- 第五步:Canvas绘画
- 第六步:SurfaceHolder.unlockCanvasAndPost(Canvas
canvas)结束锁定画图,并提交改变,将图形显示。
实例演示
下面,我们通过一个非常简单例子来实际感受一下,请留意代码中的注释:
1、在Android Studio中创建一个Android Project项目TestSurfaceView,并选择生成缺省的Activity TestSurfaceViewActivity
2、创建一个绘制线程如下:
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
importandroid.view.SurfaceHolder;
// 绘制线程
public class MyThread extends Thread
{
private SurfaceHolder holder;
private boolean run;
public MyThread(SurfaceHolder holder)
{
this.holder = holder;
run = true;
}
@Override
public void run()
{
int counter = 0;
Canvas canvas = null;
while(run)
{
// 具体绘制工作
try
{
// 获取Canvas对象,并锁定之
canvas= holder.lockCanvas();
// 设定Canvas对象的背景颜色
canvas.drawColor(Color.WHITE);
// 创建画笔
Paint p = new Paint();
// 设置画笔颜色
p.setColor(Color.BLACK);
// 设置文字大小
p.setTextSize(30);
// 创建一个Rect对象rect
Rect rect = new Rect(100, 50, 380, 330);
// 在canvas上绘制rect
canvas.drawRect(rect,p);
// 在canvas上显示时间
canvas.drawText("Interval = " + (counter++) + " seconds.", 100, 410, p);
Thread.sleep(1000);
}
catch(Exception e)
{
e.printStackTrace();
}
finally
{
if(canvas != null)
{
// 解除锁定,并提交修改内容
holder.unlockCanvasAndPost(canvas);
}
}
}
}
public boolean isRun()
{
return run;
}
public void setRun(boolean run)
{
this.run = run;
}
}
3、自定义一个SurfaceView类如下:
import android.content.Context;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback
{
private SurfaceHolder holder;
private MyThread myThread;
public MySurfaceView(Context context)
{
super(context);
// 通过SurfaceView获得SurfaceHolder对象
holder = getHolder();
// 为holder添加回调结构SurfaceHolder.Callback
holder.addCallback(this);
// 创建一个绘制线程,将holder对象作为参数传入,这样在绘制线程中就可以获得holder
// 对象,进而在绘制线程中可以通过holder对象获得Canvas对象,并在Canvas上进行绘制
myThread = new MyThread(holder);
}
// 实现SurfaceHolder.Callback接口中的三个方法,都是在主线程中调用,而不是在绘制线程中调用的
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
}
@Override
public void surfaceCreated(SurfaceHolder holder)
{
// 启动线程。当这个方法调用时,说明Surface已经有效了
myThread.setRun(true);
myThread.start();
}
@Override
public void surfaceDestroyed(SurfaceHolder holder)
{
// 结束线程。当这个方法调用时,说明Surface即将要被销毁了
myThread.setRun(false);
}
}
4、修改TestSurfaceViewActivity.java代码,使之如下:
import android.app.Activity;
import android.os.Bundle;
public class TestSurfaceViewActivity extends Activity
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
//setContentView(R.layout.main);
setContentView(new MySurfaceView(this));
}
}
http://www.360doc.com/content/13/0103/14/7724936_257842268.shtml
http://blog.youkuaiyun.com/tgww88/article/details/7973476
http://www.cnblogs.com/technology-fans/archive/2012/02/29/2373928.html