最近一段时间一直在做智能后视镜产品(Android平台)里的相机模块,目前已经告一段落了。现在空闲的时候可以把一些技术点记录下来。
其中有一个需求就是显示前、后两个摄像头的预览画面,并且可以相互切换显示。
类似下面这个样子:

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