假如想完成一个在桌面显现的悬浮窗,用DialogPopupWindowToast等现已不能完成了,他们根本都是在Activity之上显现的,假如想完成在桌面显现的悬浮窗作用,需求用到WindowManager来完成了。

作用图

Android | WindowManger实现桌面悬浮窗

使用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" />权限并且在敞开悬浮窗时动态判别权限,假如没有此权限需求跳到设置页面去设置,看下官方文档的说明:

Android | WindowManger实现桌面悬浮窗

剖析

1、增加悬浮窗: 经过Context.getSystemService(Context.WINDOW_SERVICE)取得一个WindowManager以下简称VM), VM是外界访问Window的进口,ActivityDialogToast等其视图都是依附在Window之上的,WindowView的直接管理者,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.LayoutParamsWindow的各个特点在这个内部类中设置:

  • LayoutParams.TYPE 假如TargetSdkVersion<26 ,那么可以直接使用LayoutParams.TYPE_PHONE或者LayoutParams.TYPE_SYSTEM_ALERT,在TargetSdkVersion>=26时,TYPE_PHONETYPE_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、更新悬浮窗方位: 在ViewOnTouchEvent中或OnTouch中更新layoutParams.xlayoutParams.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)viewWindow中删去即可。

问题

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…