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

前言

当我们在手机上使用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悬浮窗口的原理与具体实现技巧,具有一定参考借鉴价值,希望给大家在开发Android应用中有所帮助。

(0)

相关推荐

  • Android实现顶部悬浮效果

    本文实例为大家分享了Android实现顶部悬浮效果的具体代码,供大家参考,具体内容如下 效果图 布局 <?xml version="1.0" encoding="utf-8"?> <android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http:

  • Android仿360悬浮小球自定义view实现示例

    Android仿360悬浮小球自定义view实现示例 效果图如下: 实现当前这种类似的效果 和360小球 悬浮桌面差不错类似.这种效果是如何实现的呢.废话不多说 ,直接上代码. 1.新建工程,添加悬浮窗权限. <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> 2.自定义一个FloatMessagerMainWindow import android.content.Contex

  • Android实现桌面悬浮窗、蒙板效果实例代码

    现在很多安全类的软件,比如360手机助手,百度手机助手等等,都有一个悬浮窗,可以飘浮在桌面上,方便用户使用一些常用的操作. 今天这篇文章,就是介绍如何实现桌面悬浮窗效果的. 首先,看一下效果图. 悬浮窗一共分为两个部分,一个是平常显示的小窗口,另外一个是点击小窗口显示出来的二级悬浮窗口. 首先,先看一下这个项目的目录结构. 最关键的就是红框内的四个类. 首先,FloatWindowService是一个后台的服务类,主要负责在后台不断的刷新桌面上的小悬浮窗口,否则会导致更换界面之后,悬浮窗口也会随

  • android编程实现悬浮窗体的方法

    本文实例讲述了android编程实现悬浮窗体的方法.分享给大家供大家参考,具体如下: 突然对悬浮窗体感兴趣,查资料做了个小Demo,效果是点击按钮后,关闭当前Activity,显示悬浮窗口,窗口可以拖动,双击后消失.效果图如下: 它的使用原理很简单,就是借用了WindowManager这个管理类来实现的. 1.首先在AndroidManifest.xml中添加使用权限: 复制代码 代码如下: <uses-permission android:name="android.permission

  • android 添加随意拖动的桌面悬浮窗口

    用过新版本android 360手机助手都人都对 360中只在桌面显示一个小小悬浮窗口羡慕不已吧? 其实实现这种功能,主要有两步: 1.判断当前显示的是为桌面.这个内容我在前面的帖子里面已经有过介绍,如果还没看过的赶快稳步看一下哦. 2.使用windowManager往最顶层添加一个View .这个知识点就是为本文主要讲解的内容哦.在本文的讲解中,我们还会讲到下面的知识点: a.如果获取到状态栏的高度 b.悬浮窗口的拖动 c.悬浮窗口的点击事件 有开始之前,我们先来看一下效果图:  接下来我们来

  • Android开发悬浮按钮 Floating ActionButton的实现方法

    一.介绍 这个类是继承自ImageView的,所以对于这个控件我们可以使用ImageView的所有属性 android.support.design.widget.FloatingActionButton 二.使用准备, 在as 的 build.grade文件中写上 compile 'com.android.support:design:22.2.0' 三.使用说明 xml文件中,注意蓝色字体部分 <android.support.design.widget.FloatingActionButt

  • Android实现App中导航Tab栏悬浮的功能

    首先是"饿了么"导航Tab栏悬浮的效果图. 大家可以看到上图中的"分类"."排序"."筛选"会悬浮在app的顶部,状态随着ScrollView(也可能不是ScrollView,在这里姑且把这滑动的UI控件当作ScrollView吧)的滚动而变化.像这种导航Tab栏悬浮的作用相信大家都能体会到,Tab栏不会随着ScrollView等的滚动而被滑出屏幕外,增加了与用户之间的交互性和方便性. 看到上面的效果,相信大家都跃跃欲试了,那

  • Android滑动组件悬浮固定在顶部效果

    要想实现的效果是如下: 场景:有些时候是内容中间的组件当滑动至顶部的时候固定显示在顶部. 实现的思路: 1.目标组件(button)有两套,放在顶部和内容中间: 2.当内容中间的组件滑动至顶部栏位置时控制显示/隐藏顶部和中间的组件(涉及到组件获取在屏幕的位置知识点): activity代码: public class MainActivity extends AppCompatActivity implements ObservableScrollView.ScrollViewListener

  • Android中悬浮窗口的实现原理实例分析

    本文实例讲述了Android中悬浮窗口的实现原理.分享给大家供大家参考.具体如下: 用了我一个周末的时间,个中愤懑就不说了,就这个问题,我翻遍全球网络没有一篇像样的资料,现在将实现原理简单叙述如下: 调用WindowManager,并设置WindowManager.LayoutParams的相关属性,通过WindowManager的addView方法创建View,这样产生出来的View根据WindowManager.LayoutParams属性不同,效果也就不同了.比如创建系统顶级窗口,实现悬浮

  • Android实现类似360,QQ管家那样的悬浮窗

    一.前言: 我手机从来不装这些东西,不过,有次看到同事的android手机上,有个QQ管家在桌面上浮着,同事拖动管家时,管家就变成一只鸟,桌面下方还有个弹弓,桌面顶部有只乌鸦,把管家也就是鸟拖动到弹弓那,然后,松手,鸟就飞出去.这个过程是动画过程,做的事,实际上是清楚内存. 二:原理: 其实,没什么原理,用到的就是WindowManager以及WindowManager.LayoutParams,对这个LayoutParams做文章,当设置为属性后,然后,创建一个View,将这个View添加到W

随机推荐