精彩案例-悬浮在桌面上的照相机
2016-07-24 20:29
525 查看
一、简介
这个案例就是在桌面上开启一个悬浮窗,悬浮窗里实时显示照相机的内容,可以自由拖动,当在非桌面状态下自动隐藏.如下图所示():
PS:gif都失真了,凑合看,实际中这个窗口是不会闪烁的
我做这个是因为公司项目里在android系统的NavigationBar里显示了行车记录仪,实时录像.我想把类似的思路分享出来.通过这个可以学习TextureView和自定义悬浮窗口的知识.
二、实现
1、显示一个悬浮窗口
在MainActivity里启动一个服务,在服务里进行悬浮窗的操作
新建一个MyService继承自Service,在onCreate方法里加上如下代码
谷歌对于6.0以上的设备,默认是把悬浮窗功能给禁了,所以需要手动去打开.我用的小米就是这样,需要手动在设置里打开显示悬浮窗的权限.
以上两个都是在6.0以上的SDK里才有.
对于6.0一下的设备可以直接显示.
看下showWindow()里的代码
首先创建了MyWindow实例,这是一个自定义布局
加载布局文件进来,这个布局里放了一个Textureview和一个TextView.
创建MyWindow 实例后,获取WindowManager和布局参数LayoutParams.
这样一个Window的属性设置完了.
最后设置一个触摸监听,让悬浮窗跟随手指移动.
那么什么时候显示和隐藏悬浮窗呢?
我前面说了,会在非桌面界面隐藏该悬浮窗.在桌面界面显示悬浮窗.在这里我通过开启一个定时器每隔一秒发一个消息到Handler,在Handler里判断当前界面是否是桌面.
这样就完成了一个悬浮窗的显示和隐藏.进入其他应用时会隐藏该悬浮窗,回到桌面时又会自动显示出来.这里用到了两个方法.
2、在窗口里显示照相机
这里要用到TextureView这个控件,它和SurfaceView是 “兄弟”.
与SurfaceView相比,TextureView并没有创建一个单独的Surface用来绘制,这使得它可以像一般的View一样执行一些变换操作,设置透明度等。另外,Textureview必须在硬件加速开启的窗口中。对应的就是这条属性
以下代码是在MyWindow里实现的
设置监听需要实现SurfaceTextureListener接口,会重载下面4个方法
这里在onSurfaceTextureAvailable方法执行一下操作.
1.创建Camera实例(我这里用的是旧版本的Camera,已经过时了).
2.通过setPreviewTexture把内容渲染到SurfaceTexture 上
3.通过.setDisplayOrientation设置角度
这里用到了SetDegree(context)方法
4.通过startPreviewd开始渲染
最后onSurfaceTextureDestroyed方法里进行停止渲染和释放资源操作.
最后别忘了加上这三个权限:
三、结束
有兴趣可以自己去丰富内容,比如加拍照的功能等等.最后附上Demo:
点击打开
密码:362r
这个案例就是在桌面上开启一个悬浮窗,悬浮窗里实时显示照相机的内容,可以自由拖动,当在非桌面状态下自动隐藏.如下图所示():
PS:gif都失真了,凑合看,实际中这个窗口是不会闪烁的
我做这个是因为公司项目里在android系统的NavigationBar里显示了行车记录仪,实时录像.我想把类似的思路分享出来.通过这个可以学习TextureView和自定义悬浮窗口的知识.
二、实现
1、显示一个悬浮窗口
在MainActivity里启动一个服务,在服务里进行悬浮窗的操作
Button btn = (Button) findViewById(R.id.btn); btn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // 启动服务,在服务里开启悬浮窗 Intent intent = new Intent(MainActivity.this,MyService.class); startService(intent); } });
新建一个MyService继承自Service,在onCreate方法里加上如下代码
//对于6.0以上的设备 if (Build.VERSION.SDK_INT >= 23) { //如果支持悬浮窗功能 if (Settings.canDrawOverlays(getApplicationContext())) { showWindow(); } else { //手动去开启悬浮窗 Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); getApplicationContext().startActivity(intent); } } else { //6.0以下的设备直接开启 showWindow(); } }
谷歌对于6.0以上的设备,默认是把悬浮窗功能给禁了,所以需要手动去打开.我用的小米就是这样,需要手动在设置里打开显示悬浮窗的权限.
- Settings.canDrawOverlays(context)方法是判断当前系统是否支持悬浮窗 - Settings.ACTION_MANAGE_OVERLAY_PERMISSION 是跳转到打开悬浮窗的ACTION
以上两个都是在6.0以上的SDK里才有.
对于6.0一下的设备可以直接显示.
看下showWindow()里的代码
private void showWindow() { //创建MyWindow的实例 myWindow = new MyWindow(getApplicationContext()); //窗口管理者 mWindowManager = (WindowManager) getSystemService(Service.WINDOW_SERVICE); //窗口布局参数 Params = new WindowManager.LayoutParams(); //布局坐标,以屏幕左上角为(0,0) Params.x = 0; Params.y = 0; //布局类型 Params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; // 系统类型 //布局flags Params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; // 无焦点 Params.flags = Params.flags | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; Params.flags = Params.flags | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; //无限制布局 Params.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; //布局的gravity Params.gravity = Gravity.LEFT | Gravity.TOP; //布局的宽和高 Params.width = 500; Params.height = 500; myWindow.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: //在移动时更新坐标 Params.x = (int) event.getRawX() - myWindow.getWidth() / 2; Params.y = (int) event.getRawY() - myWindow.getHeight() / 2; //更新布局位置 mWindowManager.updateViewLayout(myWindow, Params); break; } return false; } }); }
首先创建了MyWindow实例,这是一个自定义布局
public class MyWindow extends LinearLayout implements SurfaceTextureListener { ...... public MyWindow(Context context) { super(context); LayoutInflater.from(context).inflate(R.layout.window, this); this.context = context; initView(); } ...... }
加载布局文件进来,这个布局里放了一个Textureview和一个TextView.
创建MyWindow 实例后,获取WindowManager和布局参数LayoutParams.
- 设置LayoutParams的坐标x,y - 设置LayoutParams的类型为TYPE_SYSTEM_ALERT - 设置LayoutParams的flags - 设置LayoutParams的gravity - 设置LayoutParams的宽和高
这样一个Window的属性设置完了.
最后设置一个触摸监听,让悬浮窗跟随手指移动.
public class MyService extends Service { ...... myWindow.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: Params.x = (int) event.getRawX() - myWindow.getWidth() / 2; Params.y = (int) event.getRawY() - myWindow.getHeight() / 2; //更新布局位置 mWindowManager.updateViewLayout(myWindow, Params); break; } return false; } }); ...... }
那么什么时候显示和隐藏悬浮窗呢?
我前面说了,会在非桌面界面隐藏该悬浮窗.在桌面界面显示悬浮窗.在这里我通过开启一个定时器每隔一秒发一个消息到Handler,在Handler里判断当前界面是否是桌面.
public class MyService extends Service { ...... // 定时器类 Timer timer = new Timer(); timer.schedule(task, 1000, 1000); // 1s后执行task,经过1s再次执行 ...... }
public class MyService extends Service { ...... //定时发送message给Handler TimerTask task = new TimerTask() { @Override public void run() { Message message = new Message(); handler.sendMessage(message); } }; ...... }
public class MyService extends Service { ...... private Handler handler = new Handler() { public void handleMessage(Message msg) { if (isHome()) { // 如果回到桌面,则显示悬浮窗 if (!myWindow.isAttachedToWindow()) { mWindowManager.addView(myWindow, Params); } } else { // 如果在非桌面,则去掉悬浮窗 if (myWindow.isAttachedToWindow()) { mWindowManager.removeView(myWindow); } } super.handleMessage(msg); }; }; ...... }
这样就完成了一个悬浮窗的显示和隐藏.进入其他应用时会隐藏该悬浮窗,回到桌面时又会自动显示出来.这里用到了两个方法.
- 获取桌面(Launcher)的包名的方法getHomes - 判断当前是否是桌面的方法isHome
/** * @return 获取桌面(Launcher)的包名 */ private List<String> getHomes() { List<String> names = new ArrayList<String>(); PackageManager packageManager = this.getPackageManager(); Intent intent = new Intent(Intent.ACTION_MAIN); intent.addCategory(Intent.CATEGORY_HOME); List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); for (ResolveInfo info : resolveInfo) { names.add(info.activityInfo.packageName); } return names; }
/** * @return 判断当前是否是桌面 */ public boolean isHome() { ActivityManager mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); List<RunningTaskInfo> rti = mActivityManager.getRunningTasks(1); List<String> strs = getHomes(); if (strs != null && strs.size() > 0) { return strs.contains(rti.get(0).topActivity.getPackageName()); } else { return false; } }
2、在窗口里显示照相机
这里要用到TextureView这个控件,它和SurfaceView是 “兄弟”.
与SurfaceView相比,TextureView并没有创建一个单独的Surface用来绘制,这使得它可以像一般的View一样执行一些变换操作,设置透明度等。另外,Textureview必须在硬件加速开启的窗口中。对应的就是这条属性
Params.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
以下代码是在MyWindow里实现的
private void initView() { //初始化 textureView = (TextureView) findViewById(R.id.textureView); //设置监听 textureView.setSurfaceTextureListener(this); //获取WindowManager mWindowManager = (WindowManager) context.getSystemService(Service.WINDOW_SERVICE); }
设置监听需要实现SurfaceTextureListener接口,会重载下面4个方法
- onSurfaceTextureAvailable 可用时候执行 - onSurfaceTextureDestroyed 销毁的时候执行 - onSurfaceTextureSizeChanged 尺寸改变时执行 - onSurfaceTextureUpdated 更新的时候执行 -
这里在onSurfaceTextureAvailable方法执行一下操作.
1.创建Camera实例(我这里用的是旧版本的Camera,已经过时了).
2.通过setPreviewTexture把内容渲染到SurfaceTexture 上
3.通过.setDisplayOrientation设置角度
这里用到了SetDegree(context)方法
private int SetDegree(MyWindow myWindow) { // 获得手机的方向 int rotation = mWindowManager.getDefaultDisplay().getRotation(); int degree = 0; // 根据手机的方向计算相机预览画面应该选择的角度 switch (rotation) { case Surface.ROTATION_0: degree = 90; break; case Surface.ROTATION_90: degree = 0; break; case Surface.ROTATION_180: degree = 270; break; case Surface.ROTATION_270: degree = 180; break; } return degree; }
4.通过startPreviewd开始渲染
@Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { if (myCamera == null) { // 创建Camera实例 myCamera = Camera.open(); try { // 设置预览在textureView上 myCamera.setPreviewTexture(surface); myCamera.setDisplayOrientation(SetDegree(MyWindow.this)); // 开始预览 myCamera.startPreview(); } catch (IOException e) { e.printStackTrace(); } } }
最后onSurfaceTextureDestroyed方法里进行停止渲染和释放资源操作.
@Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { myCamera.stopPreview(); //停止预览 myCamera.release(); // 释放相机资源 myCamera = null; return false; }
最后别忘了加上这三个权限:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <uses-permission android:name="android.permission.GET_TASKS" /> <uses-permission android:name="android.permission.CAMERA" />
三、结束
有兴趣可以自己去丰富内容,比如加拍照的功能等等.最后附上Demo:
点击打开
密码:362r
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories