思路实现

通过WindowManager添加一个View,创建一个系统顶级的窗口,实现悬浮窗口的效果。

本篇思路,来源于郭霖大神的悬浮窗口教程。

大致介绍WindowManager 类


创建的对象

Context.getSystemService(Context.WINDOW_SERVICE)

常用API:

  • addView():添加一个View对象

  • updateViewLayout():更新指定的View对象

  • removeView():移除一个View对象

使用Kotlin编程,实战开发


1. 编写弹窗中布局文件,item_message.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:id="@+id/suspension_window_layout"android:layout_width="wrap_content"android:layout_height="wrap_content"><TextView
         android:layout_width="wrap_content"android:text="系统悬浮弹窗"android:textColor="@android:color/white"android:textSize="18sp"android:padding="10dp"android:background="@color/colorPrimary"android:layout_height="wrap_content" />
</LinearLayout>

2. 编写悬浮弹窗的View:

定义一个布局,将对应的item_message.xml绑定上,重写onTouche()悬浮弹窗,实现自动拖动,点击关闭的效果。

class SuspensionWindowLayout(context: Context) : RelativeLayout(context) {/*** statusbar系统状态栏的高度*/var statusbarHeight = 0/*** 窗口管理器*/val windowManager: WindowManagerinit {windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManagervar childView= View.inflate(context, R.layout.item_message,this)widget_width = childView.suspension_window_layout.layoutParams.widthwidget_height = childView.suspension_window_layout.layoutParams.height}/***    按下屏幕时手指在x,y轴上的坐标*/var down_x = 0.0fvar down_y = down_x/***    移动时候的手指在x,y轴上的坐标*/var move_x = down_xvar move_y = down_x/*** 按下屏幕时候,控件在x,y轴位置*/var widget_x = down_xvar widget_y = down_x/*** 重写处理拖动事件*/override fun onTouchEvent(event: MotionEvent): Boolean {when (event.action) {MotionEvent.ACTION_DOWN -> {// 手指按下时记录必要数据,纵坐标的值都需要减去状态栏高度widget_x = event.xwidget_y = event.x//没有移动,down->up,点击事件down_x = event.rawXdown_y = event.rawY - getStatusBarHeight()move_x = event.rawXmove_y = event.rawY - getStatusBarHeight()}MotionEvent.ACTION_MOVE -> {// 手指移动的时候更新小悬浮窗的位置move_x = event.rawXmove_y = event.rawY - getStatusBarHeight()updateWidgetPostion()}MotionEvent.ACTION_UP -> {////坐标没有改变,是点击动作if (move_x == down_x && move_y == down_y) {SuspensionWindowManagerUtils.removeSuspensionWindow(context)}}else -> {}}return true}/*** 更新控件位置,在x,y轴的的位置*/fun updateWidgetPostion() {var layoutParams = SuspensionWindowManagerUtils.getWidgetLayoutParams()layoutParams!!.x = (move_x - widget_x).toInt()layoutParams!!.y = (move_y - widget_y).toInt()windowManager.updateViewLayout(this, layoutParams)}/*** 获取系统状态栏,返回状态栏高度的像素值*/fun getStatusBarHeight(): Int {if (statusbarHeight == 0) {statusbarHeight = resources.getDimensionPixelSize(ViewUtils.getStatusBarHeight())}return statusbarHeight}companion object {var widget_width = 0var widget_height = 0}
}

一个工具类

public class ViewUtils {/***  反射获取状态栏高度* @return*/public static int getStatusBarHeight(){int x=0;try {Class<?> c = Class.forName("com.android.internal.R$dimen");Object o = c.newInstance();Field field = c.getField("status_bar_height");x = (Integer) field.get(o);} catch (Exception e) {e.printStackTrace();}return  x;}
}

3. 编写WindowManager工具类

一些列,检查弹窗,开启弹窗,关闭弹窗的操作封装到该类中。

class SuspensionWindowManagerUtils {companion object {var windowManager: WindowManager?=nullvar layoutParams: WindowManager.LayoutParams?=nullvar suspensionWindowWidget: SuspensionWindowLayout? = null/*** 创建悬浮窗口*/@JvmStaticfun createSuspensionWindow(context: Context) {if (suspensionWindowWidget==null){suspensionWindowWidget= SuspensionWindowLayout(context)}getWindowManager(context)!!.addView(suspensionWindowWidget, getWidgetLayoutParams())}/*** 移除悬浮窗口*/fun removeSuspensionWindow(context: Context) {if (suspensionWindowWidget != null) {getWindowManager(context)!!.removeView(suspensionWindowWidget)suspensionWindowWidget = null}}/*** 悬浮窗口是否已经打开*/fun windowIsOpen():Boolean{if (suspensionWindowWidget!=null)return trueelse  return false}/*** 获取悬浮窗口的布局参数*/fun getWidgetLayoutParams(): WindowManager.LayoutParams? {if (layoutParams == null) {layoutParams = WindowManager.LayoutParams()layoutParams!!.type = WindowManager.LayoutParams.TYPE_PHONElayoutParams!!.format = PixelFormat.RGBA_8888layoutParams!!.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLElayoutParams!!.gravity = Gravity.LEFT or Gravity.TOPlayoutParams!!.x = windowManager!!.defaultDisplay.widthlayoutParams!!.y =0layoutParams!!.width = SuspensionWindowLayout.widget_widthlayoutParams!!.height = SuspensionWindowLayout.widget_height}return layoutParams}/*** 获取窗口管理器*/fun getWindowManager(context: Context): WindowManager ?{if (windowManager == null) {windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager}return windowManager}}
}

4. 开启一个悬浮窗口

先进行判断,若是悬浮弹窗为未开启,则进行开启。

import kotlinx.android.synthetic.main.activity_main.*class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)//点击开启悬浮窗口main_open_window.setOnClickListener {requestPermission()}}/*** 开启悬浮弹窗*/fun openSuspensionWindow(){//未开启窗口,则开启if (!SuspensionWindowManagerUtils.windowIsOpen()) {SuspensionWindowManagerUtils.createSuspensionWindow(applicationContext)}}
}

5. 在AndroidManifest.xml中添加系统弹窗权限

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

6. Android 5.1及其以下系统,运行效果:

在模拟器上运行无问题,但是在红米手机上出现问题。

红米手机需要先开启悬浮权限:显示悬浮窗–>允许。

录制效果如下:

授权 SYSTEM_ALERT_WINDOW Permission 在 android 6.0 及其以上版本的系统


运行设备

AndroidStudio 自带的模拟器,其API 24。

运行结果

在输出台上提示以下错误:

 Caused by: android.view.WindowManager$BadTokenException: Unable to add window android.view.ViewRootImpl$W@b9261f -- permission denied for window type 2002at android.view.ViewRootImpl.setView(ViewRootImpl.java:702)at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:342)at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:93)

查看SYSTEM_ALERT_WINDOW权限,可知

若是运用程序的目标API在23及其以上,程序需要通过权限管理界面,开启授权。程序发送ACTION_MANAGE_OVERLAY_PERMISSION的动作,使用Settings.canDrawOverlays()来检查是否授权。

解决方式:

1. 检查权限

使用Settings.canDrawOverlays()来检查是否授权。

/*** 当目标版本大于23时候,检查权限*/fun checkPermission():Boolean{if (Build.VERSION.SDK_INT>=23)return Settings.canDrawOverlays(this)elsereturn true}

2. 用户授权

发送ACTION_MANAGE_OVERLAY_PERMISSION,开启权限授权界面,用户授权,允许悬浮在运用程序之上。

    /*** 申请权限的状态code*/var request_code=1/*** 开启权限管理界面,授权。*/fun requestPermission(){var intent=Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:$packageName"))startActivityForResult(intent,request_code)}

3. 响应授权结果

使用Settings.canDrawOverlays()来检查授权结果,用户在管理界面是否授权。

   /*** 回调申请结果*/override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {when(requestCode) {request_code -> {if (checkPermission()) {  //用户授权成功openSuspensionWindow()} else { //用户拒绝授权Toast.makeText(application, "弹窗权限被拒绝", Toast.LENGTH_SHORT).show()}}}super.onActivityResult(requestCode, resultCode, data)}

4. 在Android6.0及其以上,运行效果

在7.0系统模拟器上,API24运行项目,效果如下

项目代码:https://github.com/13767004362/SuspensionWindowDemo

资源参考

  • 郭大大的教程:http://blog.csdn.net/guolin_blog/article/details/8689140/

Android 悬浮窗口(及解决6.0以上无法显示问题)相关推荐

  1. android 悬浮窗口和主界面同时显示,Android 悬浮窗口(及解决6.0以上无法显示问题)...

    思路实现 通过WindowManager添加一个View,创建一个系统顶级的窗口,实现悬浮窗口的效果. 本篇思路,来源于郭霖大神的悬浮窗口教程. 大致介绍WindowManager 类 创建的对象: ...

  2. Android 悬浮窗口

    Android 悬浮窗口 一.创建悬浮窗口步骤     1.实现一个ViewGroup类,作为悬浮窗口的界面类,以便在里面重写onInterceptTouchEvent和onTouchEvent方法, ...

  3. android悬浮窗口的实现

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

  4. Android悬浮窗口开发

    注意: 1. 需要Context上下文,在绝大多数非原生系统上,context上下文会影响悬浮窗的显示范围.在MIUI和华为等国产系统上,使用Activity的Context只能显示在Activity ...

  5. Android悬浮窗口

    随时随地技术实战干货,获取项目源码.学习资料,请关注源代码社区公众号(ydmsq666) FloatService: package com.home.floatwindow;import andro ...

  6. Android 悬浮窗口(一)

    前言 这章主要介绍 悬浮窗口 主要分为2种 1:悬浮在所有窗口上 2:悬浮在当前窗口上 这章讲述 悬浮在所有窗口上 1 准备 Android studio 4.1.1 或以上 win7 或以上 2 悬 ...

  7. android 悬浮窗口禁止横屏显示,悬浮窗强制设置屏幕方向|App开发交流区|研发交流|雨滴科技技术论坛 - Powered by Discuz!...

    最近在做平板上的一个程序,需要配合中通的app来控制扫描与分拣机的转动.然后中通的程序在平板上运行有一个问题, 就是app里设置了强制竖屏,不能跟随系统旋转应用屏幕方向,然后把系统里的屏幕方向写死,虽 ...

  8. android 悬浮窗口透明,悬浮视频窗口可调透明度_LG Optimus G Pro_手机Android频道-中关村在线...

    在多媒体方面,由于LG-E985T配备的高通骁龙600有着强劲的CPU.GPU.多媒体支持,所以理论上来说,LG-E985T的多媒体实力理论上来讲自然是不俗.接下来我们实际测试一下它对于高清视频播放的 ...

  9. android悬浮窗口播放动画,Android视频悬浮窗

    这个悬浮窗是一个类似于微信通话的小屏视频框,利于Service开启和保持.悬浮是利用WindowManager实现 创建FloatingWindowService服务类 public class Fl ...

最新文章

  1. oracle em 界面乱码,oracle em 按钮乱码解决办法及em网页变成英文
  2. python中or关键字在变量赋值时的用法
  3. WinFrm程序使用的图片展示控件.带删除的
  4. 华为考虑对外出售5G芯片 但对象只包括苹果公司
  5. ArcGIS Pro 简明教程(4)工具和模型构建器
  6. Windows基本磁盘结构简析(二)——MBR结构分析
  7. android实现按键找图功能,从零学起之安卓篇《按键精灵安卓版找图找色应用汇总介绍》更新20140603 _ 教程中心 - 按键精灵论坛...
  8. 通过新版阿里ACE认证,实验操作题你来解一下
  9. 南大计算机课程,南京大学 计算机系统基础 课程实验 2018(PA0-1)
  10. 在Android上将实时摄像头与AI危害检测配合使用
  11. KepServerv设置为OPCUA通讯说明
  12. [转] 同期群分析Cohort Analysis
  13. Python实现头像换脸(AI换脸)
  14. 冰点还原精灵7.0密码忘记的解决方案
  15. 荣耀十支持鸿蒙OS,不是所有华为荣耀手机都能升级华为鸿蒙OS,只有这48款才行...
  16. 分治(二)——三分法学习笔记
  17. 【笔记】拯救红米Note黑砖
  18. 记录一次oracle中count特别慢的解决方案
  19. 系统经常假死该如何解决
  20. P12:Redux进阶-将组件UI和业务逻辑进行拆分

热门文章

  1. 用php计算自由落体,JavaScript模拟自由落体
  2. PBOOTCMS后台模板管理修改插件
  3. 前端白屏问题_前端性能优化之白屏时间
  4. confirm-order提交订单
  5. 投影仪显示服务器不能连接不上,极米投影仪常见故障和自助解决方案?
  6. java疯狂讲义输入输出视频_疯狂JAVA讲义---第十五章:输入输出(上)流的处理和文件...
  7. 更新品牌与Z世代交互方式|朋氪元宇宙即将内测
  8. 使用tk.mapper和pagehelper一个bug记录:没有为 'PAGE_TABLE_ALIAS' 的列 4 指定任何列名称
  9. 手机行业影像突破,谁能成为下一个“苹果”?
  10. Error converting data type...