思路实现

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

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

大致介绍WindowManager 类

创建的对象:

Context.getSystemService(Context.WINDOW_SERVICE)

常用API:

addView():添加一个View对象

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

removeView():移除一个View对象

使用Kotlin编程,实战开发

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

android:orientation="vertical"

android:id="@+id/suspension_window_layout"

android:layout_width="wrap_content"

android:layout_height="wrap_content">

android:text="系统悬浮弹窗"

android:textColor="@android:color/white"

android:textSize="18sp"

android:padding="10dp"

android:background="@color/colorPrimary"

android:layout_height="wrap_content" />

2. 编写悬浮弹窗的View:

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

class SuspensionWindowLayout(context: Context) : RelativeLayout(context) {

/**

* statusbar系统状态栏的高度

*/

var statusbarHeight = 0

/**

* 窗口管理器

*/

val windowManager: WindowManager

init {

windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager

var childView= View.inflate(context, R.layout.item_message,this)

widget_width = childView.suspension_window_layout.layoutParams.width

widget_height = childView.suspension_window_layout.layoutParams.height

}

/**

* 按下屏幕时手指在x,y轴上的坐标

*/

var down_x = 0.0f

var down_y = down_x

/**

* 移动时候的手指在x,y轴上的坐标

*/

var move_x = down_x

var move_y = down_x

/**

* 按下屏幕时候,控件在x,y轴位置

*/

var widget_x = down_x

var widget_y = down_x

/**

* 重写处理拖动事件

*/

override fun onTouchEvent(event: MotionEvent): Boolean {

when (event.action) {

MotionEvent.ACTION_DOWN -> {

// 手指按下时记录必要数据,纵坐标的值都需要减去状态栏高度

widget_x = event.x

widget_y = event.x

//没有移动,down->up,点击事件

down_x = event.rawX

down_y = event.rawY - getStatusBarHeight()

move_x = event.rawX

move_y = event.rawY - getStatusBarHeight()

}

MotionEvent.ACTION_MOVE -> {

// 手指移动的时候更新小悬浮窗的位置

move_x = event.rawX

move_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 = 0

var 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?=null

var layoutParams: WindowManager.LayoutParams?=null

var suspensionWindowWidget: SuspensionWindowLayout? = null

/**

* 创建悬浮窗口

*/

@JvmStatic

fun 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 true

else return false

}

/**

* 获取悬浮窗口的布局参数

*/

fun getWidgetLayoutParams(): WindowManager.LayoutParams? {

if (layoutParams == null) {

layoutParams = WindowManager.LayoutParams()

layoutParams!!.type = WindowManager.LayoutParams.TYPE_PHONE

layoutParams!!.format = PixelFormat.RGBA_8888

layoutParams!!.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE

layoutParams!!.gravity = Gravity.LEFT or Gravity.TOP

layoutParams!!.x = windowManager!!.defaultDisplay.width

layoutParams!!.y =0

layoutParams!!.width = SuspensionWindowLayout.widget_width

layoutParams!!.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中添加系统弹窗权限:

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 2002

at 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)

else

return 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..net/guolin_blog/article/details/8689140/

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

  1. 1.设计一个单片机系统,可以实现对一路输入电压的测量与显示。输入电压值的范围为0~20V,显示位数为四位:十位、个位、十分位、百分位。画出相应的电路图,写出相应的程序。(提示:需要设计分压电路,待测电

    设计一个单片机系统,可以实现对一路输入电压的测量与显示.输入电压值的范围为0~20V,显示位数为四位:十位.个位.十分位.百分位.画出相应的电路图,写出相应的程序.(提示:需要设计分压电路,待测电压可 ...

  2. Android 悬浮窗口(及解决6.0以上无法显示问题)

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

  3. android ApiDemos学习1 主界面动态ListView显示

    0 Android提供了一个供开发者学习使用的示例程序.其界面如下.图中可以看到,应用列表应为ListView,看其源码发现,并非为简单的ListView,而是采用动态加载的方式. 1 主界面代码如下 ...

  4. Android项目Tab类型主界面大总结 Fragment+TabPageIndicator+ViewPager

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/24740977 Android如今实现Tab类型的界面方式越来越多,今天就把常见的 ...

  5. 如何让fragment刷新界面_快速实现android版抖音主界面的心得

    原文作者:DK_BurNIng 如何快速确定竞品某个界面的实现方式? 当你收到产品一个需求是模仿某个竞品且时间很短没有过多时间给你调研技术方案的时候,如何尽快确定这个功能的技术方案呢? 这里我给出我自 ...

  6. Swing制作高仿QQ界面包含主界面、聊天窗口、系统设置窗口|圆角界面|透明|颜色|渲染|换肤

    最近几天闲着没事,练习了一下.编写了一个模仿QQ的界面,主要是练习Swing.呵呵,基本上使用到了我博客前面讲的各种技术,在这里跟大家分享了.我们先来看看主界面:       左边的界面是用Swing ...

  7. android 开发性格测试软件,性格色彩测试android程序开发之一--主界面

    思路:主界面只有一张背景图片,两个按钮,当按钮按下的时候,按钮的颜色会发生相应的变化,按下的分成了三个状态,default,pressed和selected. 在Activity中,对button进行 ...

  8. Android实战简单新闻主界面设计

    前言 这是自己学习过程单独做出来的,挺适合新手学习,熟悉最基本的banner,imageview,listview,text等等基础控件,适合初学者,原项目共有五个模块,这个是其中一个模块 提示:以下 ...

  9. 云炬Android开发笔记 10主界面-首页UI与数据解析器开发(RecyclerView)

    阅读目录 1.创建首页UI 1.1 检查依赖是否存在 1.2 布局 1.3 控件查找 2.首页下拉刷新实现 2.1[初始化] 2.2 封装刷新功能 2.3 加载数据的处理 3. 首页数据结构分析 3. ...

最新文章

  1. P1712 [NOI2016]区间
  2. 使用keras进行深度学习_如何在Keras中通过深度学习对蝴蝶进行分类
  3. 鸿蒙轻内核定时器Swtmr:不受硬件和数量限制,满足用户需求
  4. 【css练习】斑马线表格,美人尖,断线下划线
  5. 2013年蓝桥杯题集C本科B
  6. JavaScript MD5加密实现
  7. 算法面试题(数据结构)
  8. 中呜机器人编程视频教程_中鸣快车编程入门篇—5.1补充的知识.doc
  9. VC9、VC11、VC14、VC15是啥?
  10. DirectX修复工具(DirectX Repair)是一款系统级工具软件,简便易用。本程序为绿色版,无需安装,可直接运行。 本程序的主要功能是检测当前系统的DirectX状态,如果发现异常
  11. SSD硬盘无法格式化怎么办
  12. PHP面试经常被问到的问题(附答案)
  13. 全国高等教师资格证考试复习笔记-高等教育学(1)-教育学概述
  14. 前端系列 | 2015年双11手淘前端技术巡演 - 前言
  15. Javascript - 实现Javascript控制ScrollBar(滚动条) - 学习/实践
  16. 中央一号文件力推乡村振兴,VR全景如何构建数字乡村?
  17. c语言的四大圣经,传说中的模拟电子四大圣经--值得永久珍藏
  18. java 圆周率_Java 计算圆周率
  19. 遗传算法中交叉方法简介及基于适应度的启发式多点交叉
  20. 了解下depends

热门文章

  1. linux 如何获得后缀_Bugku:杂项 linux
  2. linux netty udp服务端,Netty实现UDP服务端
  3. datax导入MySQL报错_导入MySQL方法对比
  4. python 数据处理----读取txt 一列数据写入excel 文件
  5. 工业交换机和工业路由器的区别
  6. 工业以太网交换机的安装流程详解
  7. 【渝粤教育】 国家开放大学2020年春季 1443卫生信息与文献检索 参考试题
  8. 全球最大的LoRaWAN智能路灯项目刚刚启动
  9. 布局 线宽 间距 走线 泪滴 过孔 【快速提升PCB板Layout质量的6个细节】
  10. 掌握java_如何才算掌握Java,大家都掌握到什么程度