相信大家对SurfaceView并不陌生,也相信大家一定有用它来做过视频播放等功能。
但我今天要跟大伙分享的并不是如何利用SurfaceView来做视频播放,而是想与大伙一起来谈谈SurfaceView所蕴含的美,一种只有程序员才能读懂的美。
SurfaceView作为View家族的一员,它的美是内在的,而这种内在的美又受View家族的熏陶。即继承了View的精神,但又与时俱进,不乏创新精神,标新立异,因此SurfaceView它又是一种特殊的视图,主要特殊在它拥有自己的层级(Layer)或层级缓冲区(LayerBuffer)。
因此我们可以通过下面这张图(摘自:http://blog.csdn.net/luoshengyang/article/details/8661317/)来说明下SurfaceView的实现原理:
从图上可以看到,DecorView作为视图容器,里面可以装载有:TextView控件及SurfaceView控件,那么TextView是如何在窗口上来绘制自己的UI的呢?
其实从上图我们就不难看出,它是通过SurfaceFlinger中的Layer来绘制自己的UI到宿主窗口的绘图表面上的。而至于SurfaceFlinger是什么,以及它与WindowManagerService有着什么关系,我在这里就不再赘述,不懂的,大伙可自行查找资料。总之,就是以TextView为代表的Android普通控件,它们的UI绘制是在应用程序 的主线程中进行的。但是如果你的UI很复杂或是实时性很强的,那么就有可能造成主线程的阻塞(因为应用主线程除了处理UI绘制外,还要处理用户的触摸与输入事件),从而导致程序出现ANR异常闪退。
就这样,SurfaceView顺应时事的出现了,继承了TextView绘制思想,又解决了UI很复杂时,可能造成主线程阻塞的问题,从而SurfaceView很好的被用来处理视频播放,相机预览等等,
下面我们就来看看SurfaceView之于TextView,它的不同之至到底在哪。
首先看下Google官方文档对SurfaceView给出的解释:
The surface is Z ordered so that it is behind the window holding its SurfaceView;
the SurfaceView punches a hole in its window to allow its surface to be displayed.
翻译出来大至如下(恕我英文太烂):
由于绘图表面只是在Z轴上有序排列的,因此它在宿主窗口背后持有了SurfaceView的对象引用;正是如此,SurfaceView在Z轴上挖了个“孔”,以便于展示绘图表面上所绘制的UI。
不知道大家有没有看懂我的翻译,如果没懂,我再贴出下面一张图,希望能帮助理解:
其实并不是SurfaceView在Z轴上真正的对宿主窗口表面挖了个洞,实际上,而只是在其宿主Activity窗口上设置了一块透明区域罢了。明白了SurfaceView的这一实现原理,我们就可以用它来实现很多别的功能了,比如:在人脸识别的基础上,对人脸进行虚拟妆容等,
至于SurfaceView具体的绘制过程,网上已经有大把的文章来讲述了,比如老罗的【Android视图SurfaceView的实现原理分析】,就已经讲的很详细了。那么下面,我们就结合上面所讲的,来通过一个实例看下到底如何利用SurfaceView来绘制:
假设有这样一个需求:通过人脸检测返回的人脸关键点坐标,在人脸上标出这些点,并加以编号
效果如图所示:
这里首先我们得明白几个问题:
1.可以是人脸实时检测或者人脸静态图片;
- 拿到人脸关键点数据后,如何画出这些点,并一起出来在相机框内。
人脸检测及人脸识别,这是技术性很强,专业很强的东西,非一般人所能驾驭。在这里我们可以利用市面上一些比较出名的,识别率比较高的专业公司提供的SDK来实时检测人脸(比如:Face++,商汤科技等,当然这些都是付费的)。
点坐标拿到后,就是怎么画了,画点有很多种方式,你可以通过Canvas画布来画,也可以通过OpenGLES 来画,但后者有点杀鸡用牛刀的感觉。那用Canvas怎么画呢?这个Canvas从哪里来,我们如何实例化它?
正本清源,让我们再次回到官方文档:
Access to the underlying surface is provided via the SurfaceHolder interface,
which can be retrieved by calling getHolder().
翻译如下:
SurfaceView 可以通过 getHolder()方法来获取SurfaceHolder接口实例来与宿主窗口的绘图表面surface进行通信(即在surface上进行绘制)。
The Surface will be created for you while the SurfaceView's window is visible;
you should implement surfaceCreated(SurfaceHolder) and surfaceDestroyed(SurfaceHolder) to discover when the Surface is created and destroyed as the window is shown and hidden.
翻译如下:
当SurfaceView所在宿主窗口对用户可见的时候,宿主窗口的绘图表面Surface的实例也即被创建,Surface也是有生命周期的,因此你必须得让当前宿主窗口实现SurfaceHolder接口的surfaceCreate()方法与surfaceDestroyed()方法,来判断Surface何时创建及何时销毁。
下面我们就来看下代码:
第一步:实现SurfaceHolder接口
public class SurfaceViewTestActivity extends Activity implements SurfaceHolder.Callback{
private SurfaceHoloder mSurfaceViewHoloder;
private SurfaceView mSurfaceView;
private Canvas mCanvas;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.surface_view_activity);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
//这个时候Surface就创建了,我们可以把holder引用赋给自定义的SurfaceHolder对象。
if(holoder != null )mSurfaceViewHoloder = holder;
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
//当绘制表面发生变化(比如横竖屏切换)等时调用
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
//当当前宿主窗口被销毁时调用,以结束surface.
}
}
第二步:实例化SurfaceView
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.surface_view_activity);
mSurfaceView = (SurfaceView)findViewById(R.id.surfaceview);
mSurfaceView.setZOrderOnTop(true);//这就是上面说的SurfaceView在Z轴上挖洞,
//设置绘制表面Surface的PixelFormat,这里因为是在人脸上画点,为了不遮挡人脸,我们采用
//PixelFormat.TRANSLUCENT格式,即透明的
mSurfaceHoloer.setFormat(PixelFormat.TRANSLUCENT);
//假如在这里已经返回了人脸关键点坐标(实际不是这里,为了简化流程,就在这里假如)
//然后开始画
startDrawPoints();
}
第三步:开始画人脸关键点
/***
*注意:上面说到TextView等基础控件是直接在应用主线程绘制UI,而SurfaceView是进化了的,它是专门用来处理
*复杂UI等的,即得单独开一个线程来为让其在Layer上画
**/
private void startDrawPoints(){
new Thread(){
@Ovrride
public void run(){
//这一步很关键,首先得通过SurfaceHolder来拿到Surface的Canvas
if(mSurfaceHolder != null ){
mCanvas = mSurfaceHolder.lockCanvas();
// Lock the canvas for drawing.
if (mCanvas == null) {
Log.i("WindowSurface", "Failure locking canvas");
return;
}
//这里省略各种初始化:Paint,Path等等
mCanvas .drawPath(path, mPaint);
//画完之后 ,记得刷新界面
mSurfaceHolder.unlockCanvasAndPost(mCanvas);
}
}
}.start();
}
这样几步后,最终我们就可以在手机的相机预览中看到上面类似冰冰的图了。
谢谢你终于看完了。
共同学习,写下你的评论
评论加载中...
作者其他优质文章