需求分析
在车载系统中,倒车后视一般是属于标配应用,为了能快速响应倒车事件,主要是基于 windowmanager 加载 surfaceView 来预览摄像头数据,在前两篇文章中,主要介绍了 WindowManager 和 Camera 的概念,未看过的,可以参考前两文。 Android 使用 WindowManager 实现悬浮窗监控 cpu 温度 Android Camera 开发之基础知识篇车载系统分类
根据项目的不同,车载导航系统有前装与后装之分,前装是指原车在出厂的时候就带有车载导航系统,而后装指的是车载导航系统卖给 4s 店,由用户去购买替换原有的前装导航系统。前装主要是跟车厂合作,汽车上的协议是能获取到的,比如倒车就能通过协议告诉各个终端我要倒车啦,各个终端模块做出相应的回应,比如多媒体停止播放,倒车界面显示倒车画面;而后装主要是卖给 4s 店,获取不到原车的协议(能获取,比较麻烦),像倒车这种是不经过协议通知的,而是由 mcu 监测倒车 GPIO 口,继而知道倒车事件是否发生,从而通知应用做出反应,显示倒车界面。
无论是前装项目还是后装项目,倒车事件都会经过 mcu 处理,继而通知 os 系统,再上报给 app 处理,当 app 收到倒车信号后,就要显示倒车界面。
通过上述分析,倒车画面的显示主要是预览摄像头的数据,首先想到的就是利用 surfaceView 预览 camera 数据;其次为了能快速响应倒车事件,当应用监测到有倒车事件发生时,能立即弹出界面,如果采用 Activity 的方式去做,activity 在启动过程中,会有短暂的黑屏现象,这一点是不能接受的,继而优先采用 WindowManager 来加载界面。
下面就来实现我们的预览画面:
1. 悬浮窗的创建
// windowmanager 要加载的布局
mFloatview = LayoutInflater.from(mContext).inflate(R.layout.surface_activity,null);
mLayoutParams = new WindowManager.LayoutParams();
/**
* Window type: The boot progress dialog, goes on top of everything
* in the world.
* In multiuser systems shows on all users' windows.
* @hide
* 这个 TYPE_BOOT_PROGRESS 是未公开的,能覆盖在状态栏之上
*/
mLayoutParams.type = WindowManager.LayoutParams.TYPE_BOOT_PROGRESS;
/**
* Window flag: hide all screen decorations (such as the status bar) while this window is displayed.
* This allows the window to use the entire display space for itself --
* the status bar will be hidden when an app window with this flag set is on the top layer.
* A fullscreen window will ignore a value of SOFT_INPUT_ADJUST_RESIZE for the window's softInputMode
* field; the window will stay fullscreen and will not resize.
This flag can be controlled in your theme through the windowFullscreen attribute;
this attribute is automatically set for you in the standard fullscreen themes such as
Theme_NoTitleBar_Fullscreen, Theme_Black_NoTitleBar_Fullscreen, Theme_Light_NoTitleBar_Fullscreen,
Theme_Holo_NoActionBar_Fullscreen, Theme_Holo_Light_NoActionBar_Fullscreen,
Theme_DeviceDefault_NoActionBar_Fullscreen,
and Theme_DeviceDefault_Light_NoActionBar_Fullscreen.
*//**
* 窗口显示时,隐藏所有的屏幕装饰(例如状态条)。使窗口占用整个显示区域
* 允许窗口扩展到屏幕之外。
*/
mLayoutParams.flags =WindowManager.LayoutParams.FLAG_FULLSCREEN
| WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
|FLAG_TRANSLUCENT_STATUS
|FLAG_TRANSLUCENT_NAVIGATION;
mLayoutParams.format = PixelFormat.TRANSLUCENT;
Point p = DisplayUtil.getScreenMetrics(mContext);
mLayoutParams.x = 0;
/**
* 如果存在屏幕下方还存在导航栏的话
* y 的值需要更改,更改为 -导航栏高度
*/
mLayoutParams.y = 0;
/**
* 设置长和宽
* 如果统一设置为 MATCH_PARENT 会引发不全屏的问题,如果当前界面存在状态栏,那么设置MATCH_PARENT ,
* 它的高度不是设置的高度,而是 (设备的高度-状态栏的高度)
*/if (p.x == 1024) {
mLayoutParams.width = 1024;
mLayoutParams.height = 600;
} elseif (p.x >= 1200) {
mLayoutParams.width = 1280;
mLayoutParams.height = 480;
} else {
mLayoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;
mLayoutParams.height = WindowManager.LayoutParams.MATCH_PARENT;
}
/**
* 同理,要设置全屏,如果设置为 LEFT | TOP,参考系仍然在状态栏之下,不能覆盖状态栏
*/
/
mLayoutParams.gravity = Gravity.BOTTOM;
mWindowManager.addView(mFloatview, mLayoutParams);
mSurfaceView = (CameraSurfaceView) mFloatview.findViewById(R.id.sufaceView);
2.自定义 CameraSurfaceView ,并在布局中引用
publicclassCameraSurfaceViewextendsSurfaceViewimplementsSurfaceHolder.Callback {privatestaticfinal String TAG = "Bradley";
Context mContext;
SurfaceHolder mSurfaceHolder;
private Camera mCamera;
publicCameraSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
mSurfaceHolder = getHolder();
mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
mSurfaceHolder.setFormat(PixelFormat.TRANSLUCENT);//translucent半透明 transparent透明
mSurfaceHolder.addCallback(this);
}
publicCameraSurfaceView(Context context) {
super(context);
}
@OverridepublicvoidsurfaceCreated(SurfaceHolder holder) {
// TODO Auto-generated method stub
Log.i(TAG, "surfaceCreated...");
startCamera(holder);
}
@OverridepublicvoidsurfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
// TODO Auto-generated method stubif (holder.getSurface() == null){
// preview surface does not existreturn;
}
// stop preview before making changestry {
mCamera.stopPreview();
} catch (Exception e){
// ignore: tried to stop a non-existent preview
}
// set preview size and make any resize, rotate or// reformatting changes here// start preview with new settingstry {
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
} catch (Exception e){
}
}
@OverridepublicvoidsurfaceDestroyed(SurfaceHolder holder) {
// TODO Auto-generated method stub
Log.i(TAG, "surfaceDestroyed...");
if(mCamera!=null)
mCamera.release();
}
public SurfaceHolder getSurfaceHolder(){
Log.i(TAG, "getSurfaceHolder...mSurfaceHolder = " + mSurfaceHolder);
return mSurfaceHolder;
}
/** Check if this device has a camera */privatebooleancheckCameraHardware(Context context) {
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)){
// this device has a camerareturntrue;
} else {
// no camera on this devicereturnfalse;
}
}
/** A safe way to get an instance of the Camera object. */private Camera getCameraInstance(){
Camera c = null;
try {
c = Camera.open(); // attempt to get a Camera instance
}
catch (Exception e){
// Camera is not available (in use or does not exist)
}
return c; // returns null if camera is unavailable
}
privatevoidstartCamera(SurfaceHolder holder) {
if(checkCameraHardware(mContext)){
mCamera = getCameraInstance();
if(mCamera!=null){
try {
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
代码分析:
当 CameraSurfaceView 被加载进视图后,会进行 mSurfaceHolder 的配置,同时对 SurfaceView 进行设置监听,但 SurfaceView 被创建时,打开摄像头,并对摄像头画面预览。
总结
倒车后视整体代码比较简单,但难点在于全屏显示。
1. type 的值不设置为 TYPE_SYSTEM_ALERT,主要是为了兼容考虑,及时能覆盖在状态栏之上,但在视频全屏时,有些系统状态栏会先显示出来,再缩回去,也就是状态栏会有抖动现象。
2. gravity 设置的值为 Gravity.BOTTOM ,以底部右下角为参考点,这样布局的时候能将 状态栏顶上去。
happy a nice day!