android开发之悬浮窗(WindowManager使用)

前言:悬浮窗功能属于一个独立于应用之上 在手机系统层面的一个功能,WindowManager无论应用在前台还是后台都可以显示在 当前的手机界面

悬浮窗功能 需要注意 以下几点 :

    1:悬浮窗的 权限管理,除了在常规的manifests中申请权限 和 动态权限处理 之外 ,还有就是 适配问题,在android不同的版本中 悬浮窗 获取权限都不一样。

  2: 开发 悬浮窗 步骤 无非就是, 1 .创建一个 serivce 让 悬浮窗 在 服务中,2.创建 悬浮窗 布局,3创建 WindowManager 窗口,让悬浮窗布局 加载 在 window窗口中,然后在service服务中 启动 这个 window 窗口。(在这个过程中 你会 遇到 一些 莫名其妙的坑,最让人感觉烦的就是 权限这块。需要 重点关注 我在开发过程中 在5.0 6.0.8.0上 都需要不同的处理方式 否则会 崩溃 或者 悬浮窗无法显示)

3:在开发之前 需要弄清楚 WindowManager 是 做什么用的。

 

因为悬浮窗 功能 在实际项目中,所以 无法给出全部代码,只能给出 大概的 步骤 部分 以及 悬浮窗 的核心部分,

如果需要 悬浮窗demo 请留言 或者邮箱 binbinsongstudio@outlook.com 我会尽快 提取一份demo出来分享给大家

话不多多说,直接上代码

 

首先需要创建一个 类继承servier

/**
 * 悬浮窗在服务中创建,通过暴露接口FloatCallBack与Activity进行交互
 */
public class FloatMonkService extends Service {
    /**
     * home键监听
     */
    private HomeWatcherReceiver mHomeKeyReceiver;


    @Override
    public void onCreate() {
        super.onCreate();
        //注册广播接收者
        mHomeKeyReceiver = new HomeWatcherReceiver();
        final IntentFilter homeFilter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
        registerReceiver(mHomeKeyReceiver, homeFilter);
        //初始化悬浮窗UI
        initWindowData();

    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    /**
     * 初始化WindowManager
     */
    private void initWindowData() {
        FloatWindowManager.createFloatWindow(this);

    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        //移除悬浮窗
        FloatWindowManager.removeFloatWindowManager();
        //注销广播接收者
        if (null != mHomeKeyReceiver) {
            unregisterReceiver(mHomeKeyReceiver);
        }
    }


}

HomeWatcherReceiver 类主要是用于 监听 物理按键 或者虚拟按键 接收 系统发送的广播 做出一些 属于自己的 定制话操作

/**
 * Des:一些Home建与切换键的广播监听,需要动态注册
 */

public class HomeWatcherReceiver extends BroadcastReceiver {
    private final String TAG = "HomeWatcherReceiver";
    private final String SYSTEM_DIALOG_FROM_KEY = "reason";
    private final String SYSTEM_DIALOG_FROM_RECENT_APPS = "recentapps";
    private final String SYSTEM_DIALOG_FROM_HOME_KEY = "homekey";
    private final String SYSTEM_DIALOG_FROM_LOCK = "lock";

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        Log.i(TAG, "onReceive: action: " + action);
        //根据不同的信息进行一些个性操作
        if (action.equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) {
            String from = intent.getStringExtra(SYSTEM_DIALOG_FROM_KEY);
            Log.i(TAG, "from: " + from);
            if (SYSTEM_DIALOG_FROM_HOME_KEY.equals(from)) { //短按Home键
                Log.i(TAG, "Home Key");
                //按home键会直接关闭悬浮窗
                FloatWindowManager.hide();
            } else if (SYSTEM_DIALOG_FROM_RECENT_APPS.equals(from)) { //长按Home键或是Activity切换键
                FloatWindowManager.hide();
                Log.i(TAG, "long press home key or activity switch");
            } else if (SYSTEM_DIALOG_FROM_LOCK.equals(from)) { //锁屏操作
                FloatWindowManager.hide();
                Log.i(TAG, "lock");
            }
        }
    }

}
FloatLayout 是悬浮窗 布局 文件
public class FloatLayout extends FrameLayout {
    private final WindowManager mWindowManager;
    private final ImageView mFloatView;
    //  private final DraggableFlagView mDraggableFlagView;
    private long startTime;
    private float mTouchStartX;
    private float mTouchStartY;
    private boolean isclick;
    private WindowManager.LayoutParams mWmParams;
    private Context mContext;
    private long endTime;
    // FrameLayout frameLayout;
    public static boolean isOpen = false;

    public FloatLayout(Context context) {
        this(context, null);
        mContext = context;
    }

    public FloatLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        LayoutInflater.from(context).inflate(R.layout.float_littlemonk_layout, this);
        //浮动窗口按钮
        mFloatView = (ImageView) findViewById(R.id.float_id);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // 获取相对屏幕的坐标,即以屏幕左上角为原点
        isclick = false;
        int x = (int) event.getRawX();
        int y = (int) event.getRawY();
        //下面的这些事件,跟图标的移动无关,为了区分开拖动和点击事件
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                startTime = System.currentTimeMillis();
                mTouchStartX = event.getX();
                mTouchStartY = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                //图标移动的逻辑在这里
                float mMoveStartX = event.getX();
                float mMoveStartY = event.getY();
                // 如果移动量大于3才移动
                if (Math.abs(mTouchStartX - mMoveStartX) > 3
                        && Math.abs(mTouchStartY - mMoveStartY) > 3) {
                    // 更新浮动窗口位置参数
                    mWmParams.x = (int) (x - mTouchStartX);
                    mWmParams.y = (int) (y - mTouchStartY);
                    mWindowManager.updateViewLayout(this, mWmParams);
                    return false;
                }
                break;
            case MotionEvent.ACTION_UP:
                endTime = System.currentTimeMillis();
                //当从点击到弹起小于半秒的时候,则判断为点击,如果超过则不响应点击事件
                if ((endTime - startTime) > 0.1 * 1000L) {
                    isclick = false;
                } else {
                    isclick = true;
                }
                break;
        }
        //响应点击事件
        if (isclick) {
            UcePrinterPresenter.getInstance().showBleDialog(UcePrinterPresenter.STATE);
        }
        return true;
    }

    /**
     * 将小悬浮窗的参数传入,用于更新小悬浮窗的位置。
     *
     * @param params 小悬浮窗的参数
     */
    public void setParams(WindowManager.LayoutParams params) {
        mWmParams = params;
    }


    public ImageView getmFloatView() {
        return mFloatView;
    }
}
FloatActionController 是一个控制器 主要用于 被 别人调用
/**
 * Des:与悬浮窗交互的控制类,真正的实现逻辑不在这
 */
public class FloatActionController {
    private FloatActionController() {
    }

    private UcePrinterPresenter.IPResultListener listener;

    public UcePrinterPresenter.IPResultListener getListener() {
        return listener;
    }

    public void setListener(UcePrinterPresenter.IPResultListener listener) {
        this.listener = listener;
    }

    public static FloatActionController getInstance() {
        return LittleMonkProviderHolder.sInstance;
    }

    // 静态内部类
    private static class LittleMonkProviderHolder {
        private static final FloatActionController sInstance = new FloatActionController();
    }

    private FloatCallBack mFloatCallBack;

    /**
     * 开启服务悬浮窗
     */
    public void startMonkServer(Context context) {
            Intent intent = new Intent(context, FloatMonkService.class);
            context.startService(intent);
    }

    /**
     * 判断服务是否开启
     *
     * @return
     */
    public static boolean isServiceRunning(Context context, String ServiceName) {
        if (("").equals(ServiceName) || ServiceName == null)
            return false;
        ActivityManager myManager = (ActivityManager) context
                .getSystemService(Context.ACTIVITY_SERVICE);
        ArrayList<ActivityManager.RunningServiceInfo> runningService = (ArrayList<ActivityManager.RunningServiceInfo>) myManager
                .getRunningServices(30);
        for (int i = 0; i < runningService.size(); i++) {
            if (runningService.get(i).service.getClassName().toString()
                    .equals(ServiceName)) {
                return true;
            }
        }
        return false;
    }
        /**
         * 关闭悬浮窗
         */
        public void stopMonkServer (Context context){
            Intent intent = new Intent(context, FloatMonkService.class);
            context.stopService(intent);
        }

        /**
         * 注册监听
         */
        public void registerCallLittleMonk (FloatCallBack callLittleMonk){
            mFloatCallBack = callLittleMonk;
        }

        /**
         * 悬浮窗的显示
         */
        public void show () {
            if (mFloatCallBack == null) return;
            FloatWindowManager.show();
            mFloatCallBack.show();
        }

        /**
         * 悬浮窗的隐藏
         */
        public void hide () {
            if (mFloatCallBack == null) return;
            FloatWindowManager.hide();
            mFloatCallBack.hide();
        }


    }
package com.uc56.ucexpressbao.backgroundprinting.service;

import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.PixelFormat;
import android.os.Build;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.WindowManager;

import com.thinkcore.activity.TActivityManager;
import com.uc56.ucexpressbao.R;
import com.uc56.ucexpressbao.backgroundprinting.service.permission.FloatPermissionManager;
import com.uc56.ucexpressbao.backgroundprinting.service.view.FloatLayout;
import com.uc56.ucexpressbao.backgroundprinting.service.view.PrintFloatLayout;
import com.uc56.ucexpressbao.config.BMSApplication;
import com.uc56.ucexpressbao.presenter.UcePrinterPresenter;


/**
 * 悬浮窗统一管理,与悬浮窗交互的真正实现,整个悬浮窗功能的核心
 */
public class FloatWindowManager {
    /**
     * 悬浮窗
     */
    private static FloatLayout mFloatLayout;
    private static PrintFloatLayout floatLayout;
    private static WindowManager mWindowManager;
    private static WindowManager.LayoutParams wmParams;
    private static WindowManager.LayoutParams wmPrintParams;
    private static boolean mHasShown;
    private static boolean printmHasShown;

    /**
     * 创建一个小悬浮窗。初始位置为屏幕的右部中间位置。
     *
     * @param context 必须为应用程序的Context.
     */
    public static void createFloatWindow(Context context) {

        if (FloatPermissionManager.getInstance().checkPermission(context)) {
            wmParams = new WindowManager.LayoutParams();
            WindowManager windowManager = getWindowManager(context);
            mFloatLayout = new FloatLayout(context);
            if (Build.VERSION.SDK_INT >= 26) {
                wmParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
            } else if (Build.VERSION.SDK_INT >= 24) { /*android7.0不能用TYPE_TOAST*/
                wmParams.type = WindowManager.LayoutParams.TYPE_PHONE;
            } else { /*以下代码块使得android6.0之后的用户不必再去手动开启悬浮窗权限*/
                String packname = context.getPackageName();
                PackageManager pm = context.getPackageManager();
                boolean permission = (PackageManager.PERMISSION_GRANTED == pm.checkPermission("android.permission.SYSTEM_ALERT_WINDOW", packname));
                if (permission) {
                    wmParams.type = WindowManager.LayoutParams.TYPE_PHONE;
                } else {
                    wmParams.type = WindowManager.LayoutParams.TYPE_TOAST;
                }
            }

            //设置图片格式,效果为背景透明
            wmParams.format = PixelFormat.RGBA_8888;
            //设置浮动窗口不可聚焦(实现操作除浮动窗口外的其他可见窗口的操作)
            wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
            //调整悬浮窗显示的停靠位置为左侧置顶
            wmParams.gravity = Gravity.START | Gravity.TOP;

            DisplayMetrics dm = new DisplayMetrics();
            //取得窗口属性
            mWindowManager.getDefaultDisplay().getMetrics(dm);
            //窗口的宽度
            int screenWidth = dm.widthPixels;
            //窗口高度
            int screenHeight = dm.heightPixels;
            //以屏幕左上角为原点,设置x、y初始值,相对于gravity
            wmParams.x = screenWidth - screenWidth + 50;
            wmParams.y = screenHeight - screenHeight + 50;

            //设置悬浮窗口长宽数据
            wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
            wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
            mFloatLayout.setParams(wmParams);
            windowManager.addView(mFloatLayout, wmParams);
            mHasShown = true;
            switch (UcePrinterPresenter.STATE) {
                case 1:
                    if (mFloatLayout.getmFloatView() != null)
                        mFloatLayout.getmFloatView().setImageResource(R.mipmap.print);
                    break;
                case 2:
                    if (mFloatLayout.getmFloatView() != null)
                        mFloatLayout.getmFloatView().setImageResource(R.mipmap.stop);
                    break;
                case 5:
                    if (mFloatLayout.getmFloatView() != null)
                        mFloatLayout.getmFloatView().setImageResource(R.mipmap.interrupt);
                    break;
            }
        } else {
            BMSApplication.showFloatWindowPermission(TActivityManager.getInstance().currentActivity(), R.string.open_float_window);
        }

    }


    /**
     * 移除悬浮窗
     */
    public static void removeFloatWindowManager() {
        //移除悬浮窗口
        if (mFloatLayout != null) {
            boolean isAttach = true;
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                isAttach = mFloatLayout.isAttachedToWindow();
            }
            if (mHasShown && isAttach && mWindowManager != null)
                mWindowManager.removeView(mFloatLayout);
        }
    }

    /**
     * 返回当前已创建的WindowManager。
     */
    private static WindowManager getWindowManager(Context context) {
        if (mWindowManager == null) {
            mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        }
        return mWindowManager;
    }


    public static void hide() {
        if (mHasShown)
            mWindowManager.removeViewImmediate(mFloatLayout);
        mHasShown = false;
    }

    public static void show() {
        if (!mHasShown)
            mWindowManager.addView(mFloatLayout, wmParams);
        mHasShown = true;
    }

}
FloatPermissionManager  这里做了一些 悬浮窗 权限的处理
public class FloatPermissionManager {
    private static final String TAG = "FloatPermissionManager";

    private static volatile FloatPermissionManager instance;

    private Dialog dialog;

    public static FloatPermissionManager getInstance() {
        if (instance == null) {
            synchronized (FloatPermissionManager.class) {
                if (instance == null) {
                    instance = new FloatPermissionManager();
                }
            }
        }
        return instance;
    }

    public boolean applyFloatWindow(Context context) {
        if (checkPermission(context)) {
            return true;
        } else {
            applyPermission(context);
            return false;
        }
    }

    public boolean checkPermission(Context context) {
        try {
            //6.0 版本之后由于 google 增加了对悬浮窗权限的管理,所以方式就统一了
            if (Build.VERSION.SDK_INT < 23) {
                if (RomUtils.checkIsMiuiRom()) {
                    return miuiPermissionCheck(context);
                } else if (RomUtils.checkIsMeizuRom()) {
                    return meizuPermissionCheck(context);
                } else if (RomUtils.checkIsHuaweiRom()) {
                    return huaweiPermissionCheck(context);
                } else if (RomUtils.checkIs360Rom()) {
                    return qikuPermissionCheck(context);
                }
            }
            return commonROMPermissionCheck(context);
        }catch (Exception ex){
        }
        return true;
    }

    private boolean huaweiPermissionCheck(Context context) {
        return HuaweiUtils.checkFloatWindowPermission(context);
    }

    private boolean miuiPermissionCheck(Context context) {
        return MiuiUtils.checkFloatWindowPermission(context);
    }

    private boolean meizuPermissionCheck(Context context) {
        return MeizuUtils.checkFloatWindowPermission(context);
    }

    private boolean qikuPermissionCheck(Context context) {
        return QikuUtils.checkFloatWindowPermission(context);
    }

    private boolean commonROMPermissionCheck(Context context) {
        //最新发现魅族6.0的系统这种方式不好用,天杀的,只有你是奇葩,没办法,单独适配一下
        if (RomUtils.checkIsMeizuRom()) {
            return meizuPermissionCheck(context);
        } else {
            Boolean result = true;
            if (Build.VERSION.SDK_INT >= 23) {
                try {
                    Class clazz = Settings.class;
                    Method canDrawOverlays = clazz.getDeclaredMethod("canDrawOverlays", Context.class);
                    result = (Boolean) canDrawOverlays.invoke(null, context);
                } catch (Exception e) {
                    Log.e(TAG, Log.getStackTraceString(e));
                }
            }
            return result;
        }
    }

    private void applyPermission(Context context) {
        if (Build.VERSION.SDK_INT < 23) {
            if (RomUtils.checkIsMiuiRom()) {
                miuiROMPermissionApply(context);
            } else if (RomUtils.checkIsMeizuRom()) {
                meizuROMPermissionApply(context);
            } else if (RomUtils.checkIsHuaweiRom()) {
                huaweiROMPermissionApply(context);
            } else if (RomUtils.checkIs360Rom()) {
                ROM360PermissionApply(context);
            }
        }
        commonROMPermissionApply(context);
    }

    private void ROM360PermissionApply(final Context context) {
        showConfirmDialog(context, new OnConfirmResult() {
            @Override
            public void confirmResult(boolean confirm) {
                if (confirm) {
                    QikuUtils.applyPermission(context);
                } else {
                    Log.e(TAG, "ROM:360, user manually refuse OVERLAY_PERMISSION");
                }
            }
        });
    }

    private void huaweiROMPermissionApply(final Context context) {
        showConfirmDialog(context, new OnConfirmResult() {
            @Override
            public void confirmResult(boolean confirm) {
                if (confirm) {
                    HuaweiUtils.applyPermission(context);
                } else {
                    Log.e(TAG, "ROM:huawei, user manually refuse OVERLAY_PERMISSION");
                }
            }
        });
    }

    private void meizuROMPermissionApply(final Context context) {
        showConfirmDialog(context, new OnConfirmResult() {
            @Override
            public void confirmResult(boolean confirm) {
                if (confirm) {
                    MeizuUtils.applyPermission(context);
                } else {
                    Log.e(TAG, "ROM:meizu, user manually refuse OVERLAY_PERMISSION");
                }
            }
        });
    }

    private void miuiROMPermissionApply(final Context context) {
        showConfirmDialog(context, new OnConfirmResult() {
            @Override
            public void confirmResult(boolean confirm) {
                if (confirm) {
                    MiuiUtils.applyMiuiPermission(context);
                } else {
                    Log.e(TAG, "ROM:miui, user manually refuse OVERLAY_PERMISSION");
                }
            }
        });
    }

    /**
     * 通用 rom 权限申请
     */
    private void commonROMPermissionApply(final Context context) {
        //这里也一样,魅族系统需要单独适配
        if (RomUtils.checkIsMeizuRom()) {
            meizuROMPermissionApply(context);
        } else {
            if (Build.VERSION.SDK_INT >= 23) {
                showConfirmDialog(context, new OnConfirmResult() {
                    @Override
                    public void confirmResult(boolean confirm) {
                        if (confirm) {
                            try {
                                Class clazz = Settings.class;
                                Field field = clazz.getDeclaredField("ACTION_MANAGE_OVERLAY_PERMISSION");

                                Intent intent = new Intent(field.get(null).toString());
                                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                                intent.setData(Uri.parse("package:" + context.getPackageName()));
                                context.startActivity(intent);
                            } catch (Exception e) {
                                Log.e(TAG, Log.getStackTraceString(e));
                            }
                        } else {
                            Log.d(TAG, "user manually refuse OVERLAY_PERMISSION");
                            //需要做统计效果
                        }
                    }
                });
            }
        }
    }

    private void showConfirmDialog(Context context, OnConfirmResult result) {
        showConfirmDialog(context, "您的手机没有授予悬浮窗权限,请开启后再试", result);
    }

    private void showConfirmDialog(Context context, String message, final OnConfirmResult result) {
        if (dialog != null && dialog.isShowing()) {
            dialog.dismiss();
        }

        dialog = new AlertDialog.Builder(context).setCancelable(true).setTitle("")
                .setMessage(message)
                .setPositiveButton("现在去开启",
                        new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                result.confirmResult(true);
                                dialog.dismiss();
                            }
                        }).setNegativeButton("暂不开启",
                        new DialogInterface.OnClickListener() {

                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                result.confirmResult(false);
                                dialog.dismiss();
                            }
                        }).create();
        dialog.show();
    }

    private interface OnConfirmResult {
        void confirmResult(boolean confirm);
    }
}
    原文作者:彬彬SS
    原文地址: https://blog.csdn.net/song2810106/article/details/82223897
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞