Android应用内悬浮窗的实现方案示例

1、悬浮窗的基本介绍

悬浮窗,大家应该也不陌生,凌驾于应用之上的一个小弹窗,实现上很简单,就是添加一个系统级别的窗口,Android中通过WindowManagerService( WMS)来管理所有的窗口,对于WMS来说,管你是Activity、Toast、Dialog,都不过是通过WindowManagerGlobal.addView()添加的一个个View。

Android中的窗口分为三个级别:

1.1 应用窗口,比如Activity的窗口;

1.2 子窗口,依赖于父窗口,比如PopupWindow;

1.3 系统窗口,比如状态栏、Toast,目标悬浮窗就是系统窗口.

2、根据产品需求进行设计

先了解一下大概的产品需求:

1、悬浮窗需要跨越整个应用
2、需要与悬浮窗进行交互
3、悬浮窗得移动
4、点击跳转特定的页面
5、消息提示的拖拽小红点

需求很简单,但是如果估算没错,不下一周产品经理会添加新的需求,所以为了更好的后续扩展,需要进行合理的设计,主要分为以下几点:

1、悬浮窗自定义一个FrameLayout布局FloatLayout,里面进行拖动及点击响应处理;
2、FloatMonkService,是一个服务,开启服务的时候创建悬浮窗;
3、FloatCallBack,交互接口,在FloatMonkService里面实现接口,用于交互;
4、FloatWindowManager,悬浮窗的管理,因为后续悬浮窗布局可能有好几个,可以在这里面进行切换;
5、HomeWatcherReceiver,广播接收者,因为在应用内展示,需要监听用户在点击Home键和切换键的时候隐藏悬浮窗,需要FloatMonkService里头动态注册;
6、FloatActionController,其实就是代理,其它模块需要通过它来和悬浮窗进行交互,真正干活的是实现FloatCallBack接口的FloatMonkService;
7、FloatPermissionManager,需要适配各个傻逼机型的权限,庆幸网上已有大佬分享,只需要单独对7.0系统进行一些适配就行,悬浮窗权限适配;
8、拖拽控件DraggableFlagView,直接拿来在悬浮窗上出现很奇怪的问题,所以需要改造一下下才能达到图中效果。

3、具体实现

float_littlemonk_layout.xml

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:dfv="http://schemas.android.com/apk/res-auto"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:gravity="center"
  android:orientation="vertical">

  <RelativeLayout
    android:id="@+id/monk_relative_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ImageView
      android:id="@+id/float_id"
      android:layout_width="70dp"
      android:layout_height="80dp"
      android:layout_gravity="center_vertical|end"
      android:scaleType="center"
      android:src="@drawable/little_monk" />

  </RelativeLayout>

  <FrameLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <floatwindow.xishuang.float_lib.view.DraggableFlagView
      android:id="@+id/main_dfv"
      android:layout_width="17dp"
      android:layout_height="17dp"
      android:layout_gravity="end"
      dfv:color1="#FF3B30" />
  </FrameLayout>
</FrameLayout>

简单的布局,就是一张图片+右上角放一个自定义的小红点。

FloatLayout.java

@Override
  public boolean onTouchEvent(MotionEvent event) {
    // 获取相对屏幕的坐标,即以屏幕左上角为原点
    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) {
      Toast.makeText(mContext, "我是大傻叼", Toast.LENGTH_SHORT).show();
    }
    return true;
  }

为了把悬浮窗的view操作抽离出来,自定义了这个布局,主要进行两部分功能,悬浮窗的移动和点击处理,重点是通过mWindowManager.updateViewLayout(this, mWmParams)来进行悬浮窗的位置移动,我这个Demo里面只是简单的通过时间来判断点击事件,有必要的话点击事件需要添加特定View范围判断来响应点击。

// 如果移动量大于3才移动
if (Math.abs(mTouchStartX - mMoveStartX) > 3 && Math.abs(mTouchStartY - mMoveStartY) > 3) 

这个判断是为了避免点击悬浮窗不在重心位置会出现移动的现象。

FloatMonkService.java

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

  @Override
  public void onCreate() {
    super.onCreate();
    FloatActionController.getInstance().registerCallLittleMonk(this);
    //注册广播接收者
    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);
    }
  }

  /////////////////////////////////////////////////////////实现接口////////////////////////////////////////////////////
  @Override
  public void guideUser(int type) {
    FloatWindowManager.updataRedAndDialog(this);
  }

  /**
   * 悬浮窗的隐藏
   */
  @Override
  public void hide() {
    FloatWindowManager.hide();
  }

  /**
   * 悬浮窗的显示
   */
  @Override
  public void show() {
    FloatWindowManager.show();
  }

  /**
   * 添加可领取的数量
   */
  @Override
  public void addObtainNumer() {
    FloatWindowManager.addObtainNumer(this);
    guideUser(4);
  }

  /**
   * 减少可领取的数量
   */
  @Override
  public void setObtainNumber(int number) {
    FloatWindowManager.setObtainNumber(this, number);
  }
}

服务开启的时候通过FloatWindowManager.createFloatWindow(this)来创建悬浮窗,实现FloatCallBack 实现需要交互的接口。下面看一下创建悬浮窗的真正操作是怎样的。

FloatWindowManager.java

/**
   * 创建一个小悬浮窗。初始位置为屏幕的右下角位置。
   */
  public static void createFloatWindow(Context context) {
    wmParams = new WindowManager.LayoutParams();
    WindowManager windowManager = getWindowManager(context);
    mFloatLayout = new FloatLayout(context);
    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;
    wmParams.y = screenHeight;

    //设置悬浮窗口长宽数据
    wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
    wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
    mFloatLayout.setParams(wmParams);
    windowManager.addView(mFloatLayout, wmParams);
    mHasShown = true;
    //是否展示小红点展示
    checkRedDot(context);
  }

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

核心代码其实就是mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE),其中的context不能是Activity的,一开始就说了,Activity会返回它专享的WindowManager,而Activity的窗口级别是属于应用层的。进行一些初始化操作之后 windowManager.addView(mFloatLayout, wmParams)把布局添加进去就ok了。

 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;
      }
    }

说一下这段代码的意义,当WindowManager.LayoutParams.type设置为WindowManager.LayoutParams.TYPE_TOAST的时候,是可以跳过权限申请的,但是为毛又单独适配各个机型呢,因为我们有小米Android系统,魅族Android系统,还有华为等等Android系统,特别是产品经理的魅族,一些特殊机型上是没有效果的,所以为了更保险,得再加一份权限申请,还有一点得提一下,那就是7.0上WindowManager.LayoutParams.TYPE_TOAST,悬浮窗只能持续一秒的时间,所以7.0不设这个type,谷歌爸爸最叼,7.0以上老老实实申请权限。

FloatActionController.java

/**
 * Author:xishuang
 * Date:2017.08.01
 * Des:与悬浮窗交互的控制类,真正的实现逻辑不在这
 */
public class FloatActionController {

  private FloatActionController() {
  }

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

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

  private FloatCallBack mCallLittleMonk;

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

  /**
   * 关闭悬浮窗
   */
  public void stopMonkServer(Context context) {
    Intent intent = new Intent(context, FloatMonkService.class);
    context.stopService(intent);
  }

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

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

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

这就是暴露出来的接口,按需添加,效果大概是这样的。

大概效果如下:

Demo:代码地址感兴趣可以看看完整的。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android自定义覆盖层控件 悬浮窗控件

    在我们移动应用开发过程中,偶尔有可能会接到这种需求: 1.在手机桌面创建一个窗口,类似于360的悬浮窗口,点击这个窗口可以响应(至于窗口拖动我们可以后面再扩展). 2.自己开发的应用去启动一个非本应用B,在B应用的某个界面增加一个引导窗口. 3.在应用的页面上触发启动这个窗口,该窗口悬浮在这个页面上,但又不会影响界面的其他操作.即不像PopupWindow那样要么窗口消失要么页面不可响应 以上需求都有几个共同特点,1.窗口的承载页面不一定不是本应用页面(Activity),即不是类似dialog

  • Android学习教程之悬浮窗菜单制作(9)

    本文实例为大家分享了Android悬浮窗菜单的具体代码,供大家参考,具体内容如下 MainActivity.java代码: package siso.multilistview; import android.os.Build; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; public class MainActivity extends

  • Android 悬浮窗权限各机型各系统适配大全(总结)

    这篇博客主要介绍的是 Android 主流各种机型和各种版本的悬浮窗权限适配,但是由于碎片化的问题,所以在适配方面也无法做到完全的主流机型适配,这个需要大家的一起努力,这个博客的名字永远都是一个将来时. 悬浮窗适配 悬浮窗适配有两种方法:第一种是按照正规的流程,如果系统没有赋予 APP 弹出悬浮窗的权限,就先跳转到权限授权界面,等用户打开该权限之后,再去弹出悬浮窗,比如 QQ 等一些主流应用就是这么做得:第二种就是利用系统的漏洞,绕过权限的申请,简单粗暴,这种方法我不是特别建议,但是现在貌似有些

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

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

  • Android实现带磁性的悬浮窗体效果

    本文实例讲述了Android实现带磁性的悬浮窗体效果.分享给大家供大家参考,具体如下: 带磁性的悬浮窗体,类似于360绿色小人 主要实现的是: 1.悬浮所有窗体之上 2.有吸引力,吸附于屏幕边上 3.有点击效果 下面我就实现上面三点,简单封装了个FloatView 先看下本次Demo的效果图,然后再看代码, 效果图: FloatView代码如下 package com.manymore13.flowwindowdemo; import android.content.Context; impor

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

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

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

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

  • Android手机悬浮窗口小案例

    本文实例为大家分享了Android九宫格图片展示的具体代码,供大家参考,具体内容如下 –主页面--– //布局中就一个Button public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);

  • Android应用内悬浮窗的实现方案示例

    1.悬浮窗的基本介绍 悬浮窗,大家应该也不陌生,凌驾于应用之上的一个小弹窗,实现上很简单,就是添加一个系统级别的窗口,Android中通过WindowManagerService( WMS)来管理所有的窗口,对于WMS来说,管你是Activity.Toast.Dialog,都不过是通过WindowManagerGlobal.addView()添加的一个个View. Android中的窗口分为三个级别: 1.1 应用窗口,比如Activity的窗口; 1.2 子窗口,依赖于父窗口,比如PopupW

  • Android应用内悬浮窗Activity的简单实现

    目录 前言 缩放方法 悬浮样式 点击穿透空白 移动悬浮窗 例子的完整代码 styles.xml layout activity 运行效果 小结 前言 悬浮窗是一种比较常见的需求.例如把视频通话界面缩小成一个悬浮窗,然后用户可以在其他界面上处理事情. 本文给出一个简单的应用内悬浮窗实现.可缩小activity和还原大小.可悬浮在同一个app的其他activity上.使用TouchListener监听触摸事件,拖动悬浮窗. 缩放方法 缩放activity需要使用WindowManager.Layou

  • 浅谈Android应用内悬浮控件实践方案总结

    在工作中遇到一个需求,需要在整个应用的上层悬浮显示控件,目标效果如下图: 首先想到的是申请悬浮窗权限,OK~ 打开搜索引擎,映入眼帘的并不是如何申请,而是"Android 悬浮窗权限各机型各系统适配大全.Android 绕过权限显示悬浮窗...",为什么悬浮窗权限会有这么多坑呢?悬浮窗可以在桌面显示,被恶意软件用来偷偷弹广告怎么办?作为一个系统级别的特殊权限,这是它应有的高傲 - - 正确引导用户打开悬浮窗权限才是标准做法,若这就是定论的话这篇文章也没必要写了,我们绕过悬浮窗权限直接去

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

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

  • Android超简单悬浮窗使用教程

    完全自定义悬浮窗,保证100%学会的超简单悬浮窗 先看看效果图: 图1                                                 图2                                              图3 图1只需要31行代码即可完成. 我们来看看这些都是如何实现的 使用前需要依赖库: 第一步:将以下存储库将其添加到根构建中. allprojects { repositories { ... maven { url 'https:/

  • Android编程实现悬浮窗获取并显示当前内存使用量的方法

    本文实例讲述了Android编程实现悬浮窗获取并显示当前内存使用量的方法.分享给大家供大家参考,具体如下: 运行效果: 其中: 这一块就是悬浮窗,可以随意拖动,动态显示当前内存使用量. 下面看一下代码是如何实现的: 悬浮窗的实现是用了一个service,为什么要用service呢?了解service特点的大体就会明白.下面看一下: public class FloatService extends Service { WindowManager wm = null; WindowManager.

  • Android 无障碍全局悬浮窗实现示例

    目录 无障碍添加 UI 配置分析 Type Flag LayoutInDisplayCutoutMode Android 无障碍的全局悬浮窗可以在屏幕上添加 UI 供用户进行快捷操作,可以展示在所有应用程序之上长期展示.另一方面,在一些自动化场景下,可以用来屏蔽用户行为,防止用户手动操作打断自动化流程. 无障碍添加 UI 无障碍服务添加 UI 十分简单,使用 LayoutInflater 在 AccessibilityService 的 onServiceConnected 添加一个 UI: /

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

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

  • Android view滑动悬浮固定效果实现代码示例

    1.背景 在项目开发过程中,有时候会碰到这样的需求:在滑动的过程中,在某时要将子view固定在顶部(常见的是将界面中的tab在滑动到顶部的时候进行固定). 之前写过一篇滑动组件悬浮固定在顶部的文章,但感觉还是有些复杂,因此就有了这次的实现.效果图: 2.思路 (CoordinatorLayout+AppBarLayout+CollapsingToolbarLayout)+TabLayout+ViewPager 3.代码实现 a.主布局代码 <?xml version="1.0"

  • Android仿360桌面手机卫士悬浮窗效果

    大家好,今天给大家带来一个仿360手机卫士悬浮窗效果的教程,在开始之前请允许我先说几句不相干的话. 不知不觉我发现自己接触Android已有近三个年头了,期间各种的成长少不了各位高手的帮助,总是有很多高手喜欢把自己的经验写在网上,供大家来学习,我也是从中受惠了很多,在此我深表感谢.可是我发现我却从来没有将自己平时的一些心得拿出来与大家分享,共同学习,太没有奉献精神了.于是我痛定思痛,决定从今天开始写博客,希望可以指点在我后面的开发者,更快地进入Android开发者的行列当中. 好了,废话就说这么

随机推荐