Android悬浮窗实现步骤

最近想做一个悬浮窗秒表的功能,所以看下悬浮窗具体的实现步骤

1、初识WindowManager

实现悬浮窗主要用到的是WindowManager

@SystemService(Context.WINDOW_SERVICE)
public interface WindowManager extends ViewManager {...
}

WindowManager是接口类,继承自接口ViewManager,可以通过获取WINDOW_SERVICE系统服务得到。而ViewManager接口有addView方法,我们就是通过这个方法将悬浮窗控件加入到屏幕中去。

2、设置权限

当API Level >= 23,显示悬浮窗功能,需要在清单文件AndroidManifest.xml中添加SYSTEM_ALERT_WINDOW权限,添加这个权限后才可以在其他应用上显示悬浮窗。

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

通过getSystemService方式获取WindowManager

val windowManager = getSystemService(WINDOW_SERVICE) as WindowManager

3、LayoutParam设置

WindowManager的addView方法有两个参数,一个是需要加入的控件对象,另一个参数是WindowManager.LayoutParams对象。

	// view – The view to be added to this window.// params – The LayoutParams to assign to view.public void addView(View view, ViewGroup.LayoutParams params);

其中LayoutParams的type变量,这个变量是用来指定窗口的类型。在设置这个变量时,需要对不同版本的Android系统进行适配。

        val layoutParams = WindowManager.LayoutParams()if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY} else {layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE}

在Android 8.0之前,悬浮窗口设置可以为TYPE_PHONE,这种类型是用于提供用户交互操作的非应用窗口,现在这个类型已弃用了。
而Android 8.0对系统和API行为做了修改,包括使用SYSTEM_ALERT_WINDOW权限的应用无法再使用窗口类型来在其他应用和窗口上方显示提醒窗口:

  • TYPE_PHONE(已弃用)

  • TYPE_PRIORITY_PHONE

  • TYPE_SYSTEM_ALERT

  • TYPE_SYSTEM_OVERLAY

  • TYPE_SYSTEM_ERROR

如果需要实现在其他应用和窗口上方显示提醒窗口,那么必须该为TYPE_APPLICATION_OVERLAY的新类型。

4、检测是否允许开启悬浮窗

开启悬浮窗之前,还需要检测用户是否允许开启悬浮窗,通过系统提供的canDrawOverlays来检测

//检测是否允许开启悬浮窗
Settings.canDrawOverlays(context)

如果没有允许开启,需要跳转开启页面,让用户允许开启悬浮窗

startActivity(Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION))

5、FloatingService服务

悬浮窗一直显示在其他应用上层,需要新建FloatingService服务类,用于处理悬浮窗相关逻辑。

class FloatingService : Service() {override fun onCreate() {super.onCreate()}override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {showFloatingWindow();return super.onStartCommand(intent, flags, startId)}override fun onBind(intent: Intent?): IBinder? {return null}/*** 显示悬浮窗*/private fun showFloatingWindow() {// 获取WindowManager服务val windowManager = getSystemService(WINDOW_SERVICE) as WindowManager// 新建悬浮窗控件val button = Button(applicationContext)button.text = "Floating Window"button.setBackgroundColor(Color.BLUE)// 设置LayoutParamval layoutParams = WindowManager.LayoutParams()if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY} else {layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE}layoutParams.format = PixelFormat.RGBA_8888layoutParams.flags =WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLElayoutParams.width = ActionBar.LayoutParams.WRAP_CONTENTlayoutParams.height = ActionBar.LayoutParams.WRAP_CONTENTlayoutParams.x = 300layoutParams.y = 300// 将悬浮窗控件添加到WindowManagerwindowManager.addView(button, layoutParams);}
}

6、启动FloatingService

       viewBinding.btnFloating.setOnClickListener {if (Settings.canDrawOverlays(this)) {//检测是否具有悬浮窗权限startService(Intent(this,FloatingService::class.java))} else {startActivity(Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION));}}

开启效果如下:
在这里插入图片描述

7、增加拖动功能

悬浮窗显示的位置可能会遮挡其他信息,这时就需要新增拖动功能,可以拖动到任何位置,实现的逻辑就是给布局View添加触摸事件,根据触摸和移动的位置来决定悬浮窗显示的位置。

        var x = 0var y = 0button.setOnTouchListener { view, event ->when (event.action) {MotionEvent.ACTION_DOWN -> {x = event.rawX.toInt()y = event.rawY.toInt()}MotionEvent.ACTION_MOVE -> {val nowX = event.rawX.toInt()val nowY = event.rawY.toInt()val movedX = nowX - xval movedY = nowY - yx = nowXy = nowYlayoutParams.x = layoutParams.x + movedXlayoutParams.y = layoutParams.y + movedY// 更新悬浮窗控件布局windowManager.updateViewLayout(view, layoutParams)}else -> {}}false}

8、图片自动播放

效果图如下:
在这里插入图片描述

页面布局layout_floating_image.xml如下:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="wrap_content"android:layout_height="wrap_content"><ImageViewandroid:id="@+id/imgView"android:layout_width="wrap_content"android:contentDescription="@null"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintTop_toTopOf="parent"android:layout_height="wrap_content"/></androidx.constraintlayout.widget.ConstraintLayout>

FloatingImageService服务如下:

class FloatingImageService : Service() {override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {showFloatingWindow();return super.onStartCommand(intent, flags, startId)}override fun onBind(intent: Intent?): IBinder? {return null}/*** 显示悬浮窗*/@SuppressLint("ClickableViewAccessibility", "InflateParams")private fun showFloatingWindow() {// 获取WindowManager服务val windowManager = getSystemService(WINDOW_SERVICE) as WindowManager// 获取悬浮窗布局val viewBinding = LayoutFloatingImageBinding.inflate(LayoutInflater.from(this))val imageArray = intArrayOf(R.drawable.pic1, R.drawable.pic2, R.drawable.pic3)var imageIndex = 0viewBinding.imgView.setImageResource(imageArray[imageIndex])val job = Job()val scope = CoroutineScope(job)scope.launch {while (true) {delay(2000)imageIndex++if (imageIndex == imageArray.size) {imageIndex = 0}withContext(Dispatchers.Main) {viewBinding.imgView.setImageResource(imageArray[imageIndex])}}}// 设置LayoutParamval layoutParams = WindowManager.LayoutParams()if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY} else {layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE}layoutParams.format = PixelFormat.RGBA_8888layoutParams.gravity = Gravity.START or Gravity.TOPlayoutParams.flags =WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLElayoutParams.width = ActionBar.LayoutParams.WRAP_CONTENTlayoutParams.height = ActionBar.LayoutParams.WRAP_CONTENTlayoutParams.x = 0layoutParams.y = 0// 将悬浮窗控件添加到WindowManagerwindowManager.addView(viewBinding.root, layoutParams)var x = 0var y = 0viewBinding.root.setOnTouchListener { view, event ->when (event.action) {MotionEvent.ACTION_DOWN -> {x = event.rawX.toInt()y = event.rawY.toInt()}MotionEvent.ACTION_MOVE -> {val nowX = event.rawX.toInt()val nowY = event.rawY.toInt()val movedX = nowX - xval movedY = nowY - yx = nowXy = nowYlayoutParams.x = layoutParams.x + movedXlayoutParams.y = layoutParams.y + movedY// 更新悬浮窗控件布局windowManager.updateViewLayout(view, layoutParams)}else -> {}}false}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/443093.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【CSS】css获取子元素的父元素,即通过子元素选择父元素(使用CSS伪类 :has() :not() )

这里写目录标题 一、:has获取第一个div获取包含 a.active 的 li获取第二个div 二、:not除了类名为active 的 a,其他的a的字体都为18px <div><h1>标题</h1></div><div><ul><li><a href"#" class"active">测…

k8s中调整Pod数量限制的方法

一、介绍 Kubernetes节点每个默认允许最多创建110个pod&#xff0c;有时可能由于主机配置扩容的问题&#xff0c;从而需要修改节点pod运行数量的限制。 即&#xff1a;需要调整Node节点的最大可运行Pod数量。 一般来说&#xff0c;只需要在kubelet启动命令中增加–max-pods参数…

大数据 - Hadoop系列《四》- MapReduce(分布式计算引擎)的核心思想

上一篇&#xff1a; 大数据 - Hadoop系列《三》- MapReduce&#xff08;分布式计算引擎&#xff09;概述-CSDN博客 目录 13.1 MapReduce实例进程 13.2 阶段组成 13.4 概述 13.4.1 &#x1f959;Map阶段&#xff08;映射&#xff09; 13.4.2 &#x1f959;Reduce阶段执行过…

安卓视图基础

目录 设置视图的宽高 设置视图的间隔 设置视图的对齐方式 设置视图的宽高 设置视图的间隔 设置视图的对齐方式 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"a…

【Linux】线程池的简易实现(懒汉模式)

文章目录 前言一、懒汉方式1.普通模式1.线程安全模式 二、源代码1.Task.hpp(要执行的任务)2.ThreadPool.hpp(线程池)3.Main.cpp 前言 线程池: 一种线程使用模式。线程过多会带来调度开销&#xff0c;进而影响缓存局部性和整体性能。而线程池维护着多个线程&#xff0c;等待着监…

公司如何测试员工对网络钓鱼的反应?

&#x1f4e9; "下午好&#xff0c;文件已经商定。请到下面链接的门户网站下载"。 我们每个人都可能在工作电子邮件中收到此类内容的信息&#xff1a;它可能来自真正的员工&#xff0c;也可能来自公司的信息安全服务部门&#xff0c;该部门决定对您进行检查&#xf…

vue3项目下载@element-plus/icons-vue苦笑不得的乌龙

一、背景 node.js版本&#xff1a;v16.20.1 npm版本&#xff1a;8.19.4 pnpm版本&#xff1a;8.0.0 二、心路历程 pnpm install element-plus/icons-vue 用命令下载element-plus/icons-vue的时候&#xff0c;报错并提醒如图 是&#xff0c;我按照提示执行了&#xff0c;结…

seata Adjusted frame length exceeds 8388608: 539959368,nacos+mysql+seata部署

问题&#xff1a;docker 部署 seata 后出现异常 seata Adjusted frame length exceeds 8388608: 539959368 CSDN上找了一圈都解决不了。github又半天访问不上。后来终于访问上了&#xff0c;发现这是一个很离谱的问题。。。 原因&#xff1a;访问错了端口 seata默认分两个端口…

Google Gemini Pro 国内版

Google Gemini Pro 国内版&#xff1a;【直达链接】 Google Gemini Pro 国内版 能力分类基准测试描述更高分数更好Gemini UltraGPT-4通用MMLU57个主题&#xff08;包括STEM、人文等&#xff09;的问题表示是90.0%86.4%&#xff08;5-shot, 报告&#xff09;推理Big-Bench Hard…

架构设计 高性能带来的复杂度

架构设计的主要目的是为了解决软件系统复杂度带来的问题。 复杂度来源之一就是软件的高性能。 对性能孜孜不倦的追求是整个人类技术不断发展的根本驱动力。例如计算机&#xff0c;从电子管计算机到晶体管计算机再到集成电路计算机&#xff0c;运算性能从每秒几次提升到每秒几…

OpenHarmony(鸿蒙应用开发 - 实战篇 四 ):工程目录结构。

简介 OpenHarmony是由开放原子开源基金会&#xff08;OpenAtom Foundation&#xff09;孵化及运营的开源项目&#xff0c;目标是面向全场景、全连接、全智能时代&#xff0c;基于开源的方式&#xff0c;搭建一个智能终端设备操作系统的框架和平台&#xff0c;促进万物互联产业…

毕业设计过程学习

传统的目标检测算法主要通过人工设计与纹理、颜色和形状相关的特征来进行目标区域特征的提取。随着深度学习和人工智能技术的飞速发展&#xff0c;目标检测技术也取得了很大的成就。早期基于深度学习的目标检测算法的研究方向仍然是将目标定位任务和图像分类任务分离开来的&…