Android 中 SurfaceView 的使用
一、SurfaceView 概述
在 Android 系统中,有一种特殊的视图,称为 SurfaceView。SurfaceView 本身是一个View,符合一切 View 的特性,需要通过Canvas画布绘制。
SurfaceView 拥有独立的 Surface(绘图表面)
SurfaceView 是用 Zorder 排序的,他默认在宿主 Window 的后面,SurfaceView 通过在 Window 上面“挖洞”(设置透明区域)进行显示。
SurfaceView与View的区别
- View的绘图效率不高,主要用于动画变化较少的程序
- SurfaceView 绘图效率较高,用于界面更新频繁的程序
- SurfaceView 拥有独立的 Surface(绘图表面),即它不与其宿主窗口共享同一个 Surface。
一般来说,每一个窗口在SurfaceFlinger服务中都对应有一个Layer,用来描述它的绘图表面。对于那些具有SurfaceView的窗口来说,每一个SurfaceView在SurfaceFlinger服务中还对应有一个独立的Layer或者LayerBuffer,用来单独描述它的绘图表面,以区别于它的宿主窗口的绘图表面。
因此 SurfaceView 的 UI 就可以在一个独立的线程中进行绘制,可以不会占用主线程资源。
二、双缓冲机制
SurfaceView使用双缓冲机制,播放视频时画面更流畅,那什么是双缓冲机制呢?
在运用时可以理解为:SurfaceView 在更新视图时用到了两张 Canvas,一张 frontCanvas 和一张 backCanvas ,每次实际显示的是 frontCanvas ,backCanvas 存储的是上一次更改前的视图。当你在播放这一帧的时候,它已经提前帮你加载好后面一帧了,所以播放起视频很流畅。
当使用 lockCanvas() 获取画布时,得到的实际上是 backCanvas 而不是正在显示的 frontCanvas ,之后你在获取到的 backCanvas 上绘制新视图,再 unlockCanvasAndPost(canvas)此视图,那么上传的这张 canvas 将替换原来的 frontCanvas 作为新的 frontCanvas ,原来的 frontCanvas 将切换到后台作为 backCanvas 。例如,如果你已经先后两次绘制了视图A和B,那么你再调用 lockCanvas()获取视图,获得的将是A而不是正在显示的B,之后你将重绘的 A 视图上传,那么 A 将取代 B 作为新的 frontCanvas 显示在SurfaceView 上,原来的B则转换为backCanvas。
相当与多个线程,交替解析和渲染每一帧视频数据。
三、使用场景
SurfaceView 一方面可以实现复杂而高效的 UI,另一方面又不会导致用户输入得不到及时响应。常用于画面内容更新频繁的场景,比如游戏、视频播放和相机预览。
使用 SurfaceView
1、获取 SurfaceHolder 对象,其是 SurfaceView 的内部类。添加回调监听 Surface 生命周期。
mSurfaceHolder = getHolder();
mSurfaceHolder.addCallback(this);
2、surfaceCreated 回调后启动绘制线程
只有当native层的Surface创建完毕之后,才可以调用lockCanvas(),否则失败。
@Override
public void surfaceCreated(SurfaceHolder holder) {
mDrawThread = new DrawThread();
mDrawThread.start();
}
3、绘制
Canvas canvas = mSurfaceHolder.lockCanvas();
// 使用canvas绘制内容
...
mSurfaceHolder.unlockCanvasAndPost(canvas);
使用SurfaceView不显示问题
发生这种问题的原因是多层嵌套被遮挡
解决方法是根据具体情况调用如下api接口:
setZOrderOnTop(boolean onTop) // 在最顶层,会遮挡一切view
setZOrderMediaOverlay(boolean isMediaOverlay)// 如已绘制SurfaceView则在surfaceView上一层绘制。
看下他们的源码:
public void setZOrderMediaOverlay(boolean isMediaOverlay) {
mSubLayer = isMediaOverlay
? APPLICATION_MEDIA_OVERLAY_SUBLAYER : APPLICATION_MEDIA_SUBLAYER;
}
public void setZOrderOnTop(boolean onTop) {
if (onTop) {
mSubLayer = APPLICATION_PANEL_SUBLAYER;
} else {
mSubLayer = APPLICATION_MEDIA_SUBLAYER;
}
}
两个方法都是给mSubLayer赋值,所以需要注意这两个接口同时调用后一个会覆盖前一个的效果。
黑色背景问题
//设置背景透明
mHolder.setFormat(PixelFormat.TRANSPARENT);