目录
如果想实现一个在桌面显示的悬浮窗,用dialog
、popupwindow
、toast
等已经不能实现了,他们基本都是在activity
之上显示的,如果想实现在桌面显示的悬浮窗效果,需要用到windowmanager
来实现了。
效果图
使用windowmanager实现
- 添加一个悬浮窗:
sys_view = new smallwindowview(mcontext); sys_view.settext("50%"); sys_view.setontouchlistener(this); windowmanager = (windowmanager) mcontext.getsystemservice(context.window_service); int screenwidth = 0, screenheight = 0; if (windowmanager != null) { //获取屏幕的宽和高 point point = new point(); windowmanager.getdefaultdisplay().getsize(point); screenwidth = point.x; screenheight = point.y; layoutparams = new windowmanager.layoutparams(); // layoutparams.width = windowmanager.layoutparams.wrap_content; // layoutparams.height = windowmanager.layoutparams.wrap_content; layoutparams.width = 200; layoutparams.height = 200; //设置type if (build.version.sdk_int >= build.version_codes.o) { //26及以上必须使用type_application_overlay @deprecated type_phone layoutparams.type = windowmanager.layoutparams.type_application_overlay; } else { layoutparams.type = windowmanager.layoutparams.type_phone; } //设置flags layoutparams.flags = windowmanager.layoutparams.flag_not_touch_modal | windowmanager.layoutparams.flag_not_focusable | windowmanager.layoutparams.flag_show_when_locked; layoutparams.gravity = gravity.start | gravity.top; //背景设置成透明 layoutparams.format = pixelformat.transparent; layoutparams.x = screenwidth; layoutparams.y = screenheight / 2; //将view添加到屏幕上 windowmanager.addview(sys_view, layoutparams); }
- 更新悬浮窗位置:
windowmanager.updateviewlayout(sys_view, layoutparams);
- 关闭悬浮窗:
windowmanager.removeview(sys_view);
通过上面的代码就可以实现一个桌面悬浮窗功能了。
注意:在6.0
以上,需要在manifest.xml
中声明
权限并且在开启悬浮窗时动态判断权限,如果没有此权限需要跳到设置页面去设置,看下官方文档的说明:
分析
1、添加悬浮窗: 通过context.getsystemservice(context.window_service)
获得一个windowmanager
(以下简称vm), vm
是外界访问window
的入口,activity
、dialog
、toast
等其视图都是依附在window
之上的,window
是view
的直接管理者,vm
继承自viewmanager
,其添加、刷新、删除方法也是来自viewmanager
:
public interface viewmanager { public void addview(view view, viewgroup.layoutparams params); public void updateviewlayout(view view, viewgroup.layoutparams params); public void removeview(view view); }
vm
有一个静态内部类windowmanager.layoutparams
,window
的各个属性在这个内部类中设置:
- layoutparams.type 如果
targetsdkversion<26
,那么可以直接使用layoutparams.type_phone
或者layoutparams.type_system_alert
,在targetsdkversion>=26
时,type_phone
和type_system_alert
都已经废弃了,需要使用type_application_overlay
来标识type
。 - layoutparams.flags
flags
表示window
的属性,通过flags
可以控制window
的显示特性,常用的几个特性:layoutparams.flag_not_touch_modal
: 使用了此标识,可以将点击事件传递到悬浮窗以外的区域,反之其他区域的window
将接收不到事件。layoutparams.flag_not_focusable
: 表示悬浮窗window
不需要获取焦点,也不需要获取各种输入事件,事件会直接传递给下层的具有焦点的window
layoutparams.flag_show_when_locked
: 此模式可以让window
显示在锁屏的界面上 - layoutparams.format 悬浮窗window的背景格式,一般设置成
pixelformat.transparent
透明即可 - layoutparams.x & layoutparams.y 悬浮窗
window
在屏幕上的坐标值,可以根据x&y
的值来刷新window
在屏幕上的位置 - layoutparams.width & layoutparams.height 悬浮窗
window
的宽度和高度
2、更新悬浮窗位置: 在view
的ontouchevent
中或ontouch
中更新layoutparams.x
及layoutparams.y
的值并通过windowmanager.updateviewlayout()
重新设置悬浮窗window在屏幕中的位置,如下:
@override public boolean ontouch(view v, motionevent event) { int minscreenx = (int) event.getrawx(); int minscreeny = (int) event.getrawy(); switch (event.getaction()) { case motionevent.action_down: mlastx = (int) event.getrawx(); mlasty = (int) event.getrawy(); break; case motionevent.action_move: layoutparams.x = minscreenx - mlastx; layoutparams.y = minscreeny - mlasty; mlastx = minscreenx; mlasty = minscreeny; windowmanager.updateviewlayout(sys_view, layoutparams); break; case motionevent.action_up: break; } return true; }
3、删除悬浮窗: 删除比较简单,直接调用windowmanager.removeview(view)
把view
从window
中删除即可。
问题
在6.0以上
使用时,需要动态申请该悬浮窗权限,如下:
//判断有没有悬浮窗权限,没有去申请 if(!settings.candrawoverlays(context)){ intent intent = new intent(settings.action_manage_overlay_permission, uri.parse("package:" context.getpackagename())); context.startactivityforresult(intent, request_code); } @override protected void onactivityresult(int requestcode, int resultcode, intent data) { switch (requestcode) { case request_code: if (build.version.sdk_int < build.version_codes.m) return; if (!windowutil.canoverdraw(this)) { toast("悬浮窗权限未开启,请在设置中手动打开"); return; } windowcontroller.getinstance().showthumbwindow(); break; } }
通过settings.candrawoverlays(context)
判断是否有悬浮窗权限,如果没有,跳转到设置页面去设置,并在onactivityresult ()
中得到申请结果,看似很完美,但在实际测试中,发现在8.0以上的手机上有问题,即使在设置中同意了权限,8.0的手机settings.candrawoverlays(context)
总是返回false
,不过在关闭页面重新调用此方法时,又返回的true
,感觉是有一定的延迟,google
了一下,发现别人同样遇到了这个问题,貌似已经给google
提交了bug
单,可以看此博客: http://paskov.vmsoft-bg.com/settings-candrawoverlays-allays-returns-false-on-android-o/
,不过博客中的解决方法用我的8.0手机
(huawei mate10
)依然不起作用,暂时还没深入研究,有解决此问题的还希望不吝赐教。
以上例子的源码地址:https://github.com/crazyqiang
参考
【1】