不依赖于Activity的Android全局悬浮窗的实现

作者:daisy 时间:2022-04-08 00:42:43 

前言

当我们在手机上使用360安全卫士时,手机屏幕上时刻都会出现一个小浮动窗口,点击该浮动窗口可跳转到安全卫士的操作界面,而且该浮动窗口不受其他activity的覆盖影响仍然可见(多米音乐也有相关的和主界面交互的悬浮小窗口)。那么这种不受Activity界面影响的悬浮窗口是怎么实现的呢?

Android悬浮窗实现

实现基础

Android悬浮窗实现使用WindowManager
WindowManager介绍
通过Context.getSystemService(Context.WINDOW_SERVICE)可以获得 WindowManager对象。

每一个WindowManager对象都和一个特定的 Display绑定。

想要获取一个不同的display的WindowManager,可以用 createDisplayContext(Display)来获取那个displayContext,之后再使用:Context.getSystemService(Context.WINDOW_SERVICE)来获取WindowManager

使用WindowManager可以在其他应用最上层,甚至手机桌面最上层显示窗口。

调用的是WindowManager继承自基类的addView方法和removeView方法来显示和隐藏窗口。具体见后面的实例。

另:API 17推出了Presentation,它将自动获取displayContext和WindowManager,可以方便地在另一个display上显示窗口。

WindowManager实现悬浮窗需要声明权限

首先在manifest中添加如下权限:


<!-- 显示顶层浮窗 --><uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

注意:在MIUI上需要在设置中打开本应用的”显示悬浮窗”开关,并且重启应用,否则悬浮窗只能显示在本应用界面内,不能显示在手机桌面上。

服务获取和基本参数设置


[java] view plain copy print?在CODE上查看代码片派生到我的代码片
// 获取应用的Context
mContext = context.getApplicationContext();
// 获取WindowManager
mWindowManager = (WindowManager) mContext
    .getSystemService(Context.WINDOW_SERVICE);
参数设置:
final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
// 类型
params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
// WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
// 设置flag
int flags = WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
// | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
// 如果设置了WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,弹出的View收不到Back键的事件
params.flags = flags;
// 不设置这个弹出框的透明遮罩显示为黑色
params.format = PixelFormat.TRANSLUCENT;
// FLAG_NOT_TOUCH_MODAL不阻塞事件传递到后面的窗口
// 设置 FLAG_NOT_FOCUSABLE 悬浮窗口较小时,后面的应用图标由不可长按变为可长按
// 不设置这个flag的话,home页的划屏会有问题
params.width = LayoutParams.MATCH_PARENT;
params.height = LayoutParams.MATCH_PARENT;
params.gravity = Gravity.CENTER;

点击和按键事件

除了View中的各个控件的点击事件之外,弹窗View的消失控制需要一些处理。

点击弹窗外部可隐藏弹窗的效果,首先,悬浮窗是全屏的,只不过最外层的是透明或者半透明的:

具体实现


[java] view plain copy print?在CODE上查看代码片派生到我的代码片
package com.robert.floatingwindow;
import android.content.Context;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnKeyListener;
import android.view.View.OnTouchListener;
import android.view.WindowManager;
import android.view.View.OnClickListener;
import android.view.WindowManager.LayoutParams;
import android.widget.Button;
/**
* 弹窗辅助类
*
* @ClassName WindowUtils
*
*
*/
public class WindowUtils {
 private static final String LOG_TAG = "WindowUtils";
 private static View mView = null;
 private static WindowManager mWindowManager = null;
 private static Context mContext = null;
 public static Boolean isShown = false;
 /**
  * 显示弹出框
  *
  * @param context
  * @param view
  */
 public static void showPopupWindow(final Context context) {
   if (isShown) {
     LogUtil.i(LOG_TAG, "return cause already shown");
     return;
   }
   isShown = true;
   LogUtil.i(LOG_TAG, "showPopupWindow");
   // 获取应用的Context
   mContext = context.getApplicationContext();
   // 获取WindowManager
   mWindowManager = (WindowManager) mContext
       .getSystemService(Context.WINDOW_SERVICE);
   mView = setUpView(context);
   final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
   // 类型
   params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
   // WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
   // 设置flag
   int flags = WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
   // | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
   // 如果设置了WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,弹出的View收不到Back键的事件
   params.flags = flags;
   // 不设置这个弹出框的透明遮罩显示为黑色
   params.format = PixelFormat.TRANSLUCENT;
   // FLAG_NOT_TOUCH_MODAL不阻塞事件传递到后面的窗口
   // 设置 FLAG_NOT_FOCUSABLE 悬浮窗口较小时,后面的应用图标由不可长按变为可长按
   // 不设置这个flag的话,home页的划屏会有问题
   params.width = LayoutParams.MATCH_PARENT;
   params.height = LayoutParams.MATCH_PARENT;
   params.gravity = Gravity.CENTER;
   mWindowManager.addView(mView, params);
   LogUtil.i(LOG_TAG, "add view");
 }
 /**
  * 隐藏弹出框
  */
 public static void hidePopupWindow() {
   LogUtil.i(LOG_TAG, "hide " + isShown + ", " + mView);
   if (isShown && null != mView) {
     LogUtil.i(LOG_TAG, "hidePopupWindow");
     mWindowManager.removeView(mView);
     isShown = false;
   }
 }
 private static View setUpView(final Context context) {
   LogUtil.i(LOG_TAG, "setUp view");
   View view = LayoutInflater.from(context).inflate(R.layout.popupwindow,
       null);
   Button positiveBtn = (Button) view.findViewById(R.id.positiveBtn);
   positiveBtn.setOnClickListener(new OnClickListener() {
     @Override
     public void onClick(View v) {
       LogUtil.i(LOG_TAG, "ok on click");
       // 打开安装包
       // 隐藏弹窗
       WindowUtils.hidePopupWindow();
     }
   });
   Button negativeBtn = (Button) view.findViewById(R.id.negativeBtn);
   negativeBtn.setOnClickListener(new OnClickListener() {
     @Override
     public void onClick(View v) {
       LogUtil.i(LOG_TAG, "cancel on click");
       WindowUtils.hidePopupWindow();
     }
   });
   // 点击窗口外部区域可消除
   // 这点的实现主要将悬浮窗设置为全屏大小,外层有个透明背景,中间一部分视为内容区域
   // 所以点击内容区域外部视为点击悬浮窗外部
   final View popupWindowView = view.findViewById(R.id.popup_window);// 非透明的内容区域
   view.setOnTouchListener(new OnTouchListener() {
     @Override
     public boolean onTouch(View v, MotionEvent event) {
       LogUtil.i(LOG_TAG, "onTouch");
       int x = (int) event.getX();
       int y = (int) event.getY();
       Rect rect = new Rect();
       popupWindowView.getGlobalVisibleRect(rect);
       if (!rect.contains(x, y)) {
         WindowUtils.hidePopupWindow();
       }
       LogUtil.i(LOG_TAG, "onTouch : " + x + ", " + y + ", rect: "
           + rect);
       return false;
     }
   });
   // 点击back键可消除
   view.setOnKeyListener(new OnKeyListener() {
     @Override
     public boolean onKey(View v, int keyCode, KeyEvent event) {
       switch (keyCode) {
       case KeyEvent.KEYCODE_BACK:
         WindowUtils.hidePopupWindow();
         return true;
       default:
         return false;
       }
     }
   });
   return view;
 }
}

总结

标签:android,悬浮窗,activity
0
投稿

猜你喜欢

  • swagger如何返回map字段注释

    2023-02-22 08:56:27
  • Android简单创建一个Activity的方法

    2023-01-22 12:39:53
  • C#实现套接字发送接收数据

    2023-01-09 19:34:38
  • Java 深入探讨设计模式之原型模式篇

    2023-11-16 17:37:59
  • C#中的尾递归与Continuation详解

    2022-07-27 04:14:05
  • C#写入XML文档

    2022-03-21 18:08:33
  • 基于Android中的 AutoCompleteTextView实现自动填充

    2023-04-02 07:11:36
  • C#获得程序的根目录以及判断文件是否存在的实例讲解

    2022-12-07 18:00:27
  • C# 骑士飞行棋的源码(分享)

    2021-10-11 02:54:56
  • java8 stream多字段排序的实现

    2021-07-30 17:47:47
  • asp.net实现遍历Request的信息操作示例

    2022-11-15 23:15:18
  • Java实现获取客户端真实IP方法小结

    2021-10-15 03:53:16
  • Java打印九九乘法表代码详情

    2022-03-14 17:42:14
  • Android中Fragment的基本用法示例总结

    2021-11-25 19:00:23
  • Android 添加系统设置属性的实现及步骤

    2021-12-31 01:11:02
  • Java多线程run方法中直接调用service业务类应注意的问题及解决

    2021-12-28 19:51:46
  • C#中实现AES算法加密解读

    2022-09-17 16:49:36
  • mybatis-plus之如何实现in嵌套sql

    2023-02-11 14:57:57
  • Java读取、写入文件如何解决乱码问题

    2023-08-26 11:16:00
  • Java实现计算器设计

    2023-08-18 13:36:54
  • asp之家 软件编程 m.aspxhome.com