为了账号安全,请及时绑定邮箱和手机立即绑定

漫说Android 中SurfaceView蕴含的美

标签:
Android

相信大家对SurfaceView并不陌生,也相信大家一定有用它来做过视频播放等功能。

但我今天要跟大伙分享的并不是如何利用SurfaceView来做视频播放,而是想与大伙一起来谈谈SurfaceView所蕴含的美,一种只有程序员才能读懂的美。

SurfaceView作为View家族的一员,它的美是内在的,而这种内在的美又受View家族的熏陶。即继承了View的精神,但又与时俱进,不乏创新精神,标新立异,因此SurfaceView它又是一种特殊的视图,主要特殊在它拥有自己的层级(Layer)层级缓冲区(LayerBuffer)
因此我们可以通过下面这张图(摘自:http://blog.csdn.net/luoshengyang/article/details/8661317/)来说明下SurfaceView的实现原理

图1:SurfaceView及其宿主Activity窗口的绘制表面示意图

从图上可以看到,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.可以是人脸实时检测或者人脸静态图片;

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

这样几步后,最终我们就可以在手机的相机预览中看到上面类似冰冰的图了。
谢谢你终于看完了。

点击查看更多内容
2人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消