假如想完成一个在桌面显现的悬浮窗,用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
中声明
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
权限并且在敞开悬浮窗时动态判别权限,假如没有此权限需求跳到设置页面去设置,看下官方文档的说明:
剖析
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】developer.android.com/reference/a…