天天看点

Android两个SurfaceView叠加显示

最近一段时间一直在做智能后视镜产品(Android平台)里的相机模块,目前已经告一段落了。现在空闲的时候可以把一些技术点记录下来。

其中有一个需求就是显示前、后两个摄像头的预览画面,并且可以相互切换显示。

类似下面这个样子:

Android两个SurfaceView叠加显示

在Android里常用来显示相机预览的就是SurfaceView,因为不需要在UI线程刷新。那这里我们考虑的就是将两个SurfaceView叠加在一起显示。

布局xml(部分):

<FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"  android:layout_gravity="center_vertical|center_horizontal" >      
        < SurfaceView
            android:id="@+id/usb_preview_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
        <SurfaceView
            android:id="@+id/mipi_preview_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </FrameLayout>
           

这样子就可以把两个SurfaceView以全屏的形式叠加在一起了。

那问题是怎样去切换这两个界面呢?

像普通的view一样设置其可见性setVisibility,将上层的SurfaceView设置不可见就可以显示下层的SurfaceView了。

但是实际是不可行的,SurfaceView和普通的View不一样,它拥有一个独立于Activity之外的Window,当SurfaceView被设置为不可见的时候,其所对应的Window就会销毁,再想去显示这个SurfaceView的时候就不得不重新创建;另外,SurfaceView创建的先后顺序也是不受FrameLayout的规则影响的,一般来说,布局xml文件中FrameLayout布局靠后的view会覆盖考前的view,但SurfaceView是根据其对应的Surface创建的先后顺序决定的。

那另一种思路就是,两个SurfaceView叠在一起后,将上层的SurfaceView所在Window设置成透明的,如果需要显示下面SurfaceView的内容,就将上层SurfaceView的绘制区域缩小到1、2像素的范围,肉眼看不见,显示的就是下层SurfaceView的内容了;显示上层SurfaceView的时候,就全屏绘制了。这里SurfaceView对应Window的大小是不会动态改变的,改变的只是图像绘制区域的大小。

设置透明的方法,是通过SurfaceHolder来设置的,注意调用的位置,否则无效。

mSurfaceView.getHolder().setFormat(PixelFormat.TRANSPARENT);

API文档里的:

public abstract void setFormat (int format)

****Added in API level 1

Set the desired PixelFormat of the surface. The default is OPAQUE. When working with a SurfaceView, this must be called from the same thread running the SurfaceView’s window.**

这种方式实际是可行的,但是也有局限性。Android框架上,我们只需初始化SurfaceHolder,再通过Camera中setPreviewDisplay(holder)后启动预览,就可以自动display the surface了。也就是说,如果想改变SurfaceView绘制的区域,就得重新设置大小和位置,并重新启动相机预览,比较耗时,而且在后台录像的情况下是不可行的。

还好我们这里在上层的SurfaceView是用来显示USB摄像头的预览,就是在Canvas上将图像一帧帧绘制出来的。

Canvas canvas = getHolder().lockCanvas();
if (canvas != null) {
    if (mCurrentPreviewState == USB_PREVIEW) {
        rect.set(IMG_WIDTH / , IMG_HEIGHT / , IMG_WIDTH, IMG_HEIGHT);
        canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
        canvas.drawBitmap(mPreviewBitmap, null, rect, null);
    } else {
        canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
        canvas.drawBitmap(mPreviewBitmap, null, rect, null);
    }                   
    getHolder().unlockCanvasAndPost(canvas);
}