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)

        // 设置LayoutParam
        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
        }

        layoutParams.format = PixelFormat.RGBA_8888
        layoutParams.flags =
            WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
        layoutParams.width = ActionBar.LayoutParams.WRAP_CONTENT
        layoutParams.height = ActionBar.LayoutParams.WRAP_CONTENT
        layoutParams.x = 300
        layoutParams.y = 300

        // 将悬浮窗控件添加到WindowManager
        windowManager.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 = 0
        var y = 0
        button.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 - x
                    val movedY = nowY - y
                    x = nowX
                    y = nowY
                    layoutParams.x = layoutParams.x + movedX
                    layoutParams.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">

    <ImageView
        android: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 = 0
        viewBinding.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])
                }
            }
        }

        // 设置LayoutParam
        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
        }

        layoutParams.format = PixelFormat.RGBA_8888
        layoutParams.gravity = Gravity.START or Gravity.TOP
        layoutParams.flags =
            WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
        layoutParams.width = ActionBar.LayoutParams.WRAP_CONTENT
        layoutParams.height = ActionBar.LayoutParams.WRAP_CONTENT
        layoutParams.x = 0
        layoutParams.y = 0

        // 将悬浮窗控件添加到WindowManager
        windowManager.addView(viewBinding.root, layoutParams)


        var x = 0
        var y = 0
        viewBinding.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 - x
                    val movedY = nowY - y
                    x = nowX
                    y = nowY
                    layoutParams.x = layoutParams.x + movedX
                    layoutParams.y = layoutParams.y + movedY

                    // 更新悬浮窗控件布局
                    windowManager.updateViewLayout(view, layoutParams)
                }

                else -> {}
            }
            false
        }
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于springboot招生管理系统源码和论文

在Internet高速发展的今天&#xff0c;我们生活的各个领域都涉及到计算机的应用&#xff0c;其中包括招生管理系统的网络应用&#xff0c;在外国招生管理系统已经是很普遍的方式&#xff0c;不过国内的管理网站可能还处于起步阶段。招生管理系统具有招生公告信息管理功能的选择…

三款精选数字孪生产品大比拼

作为一名数据可视化领域的资深用户&#xff0c;我接触过众多数据可视化产品。本文将介绍三款备受关注的数据可视化工具&#xff0c;并对它们进行详细的比较。 首先&#xff0c;让我们了解一下数据可视化产品的核心价值。在信息爆炸的时代&#xff0c;数据可视化成为快速理解复…

Kubernetes k8s

Kubernetes k8s 一个开源的容器编排引擎&#xff0c;用来对容器化应用进行自动化部署、 扩缩和管理。 从架构设计层面&#xff0c;k8s能很好的解决可用性&#xff0c;伸缩性&#xff1b;从部署运维层面&#xff0c;服务部署&#xff0c;服务监控&#xff0c;应用扩容和故障处…

C++——特殊类

特殊类 文章目录 特殊类一、请设计一个类&#xff0c;不能被拷贝二、请设计一个类&#xff0c;只能在堆上创建对象方案一&#xff1a;析构函数私有化方案二&#xff1a;构造函数私有化 三、请设计一个类&#xff0c;只能在栈上创建对象四、请设计一个类&#xff0c;不能被继承五…

互联网加竞赛 基于深度学习的人脸性别年龄识别 - 图像识别 opencv

文章目录 0 前言1 课题描述2 实现效果3 算法实现原理3.1 数据集3.2 深度学习识别算法3.3 特征提取主干网络3.4 总体实现流程 4 具体实现4.1 预训练数据格式4.2 部分实现代码 5 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 毕业设计…

初学者在Python中的基本图像处理库 - OpenCV和imutils

处理图像处理和操作的最常用的库之一是 Python 的 OpenCV。对于图像分类、目标检测或光学字符识别&#xff0c;在人工智能领域与图像相关的任何工作大多数时候都需要某种形式的图像处理和操作。 在本教程中&#xff0c;我们将专注于 OpenCV 的一些基本功能。这些功能基础且有时…

tcpdump在手机上的使用

首先手机得root才可以&#xff0c;主要分析手机与手机的通信协议 我使用的是一加9pro&#xff0c; root方法参考一加全能盒子、一加全能工具箱官方网站——大侠阿木 (daxiaamu.com)https://optool.daxiaamu.com/index.php tcpdump&#xff0c;要安装在/data/local/tmp下要arm6…

ES(ElasticSearch)技术栈简介

ElasticSearch简介 Elaticsearch&#xff0c;简称为es&#xff0c; es是一个基于apache开源的高扩展的分布式全文检索引擎&#xff0c;它可以近乎实时的存储、检索数据&#xff1b;本身扩展性很好&#xff0c;可以扩展到上百台服务器&#xff0c;处理PB级别的数据。es也使用Ja…

机器学习系列-2 线性回归训练损失

机器学习系列-2 线性回归&训练损失 学习内容来自&#xff1a;谷歌ai学习 https://developers.google.cn/machine-learning/crash-course/framing/check-your-understanding?hlzh-cn 本文作为学习记录1 线性回归&#xff1a; 举例&#xff1a;蝉&#xff08;昆虫物种&…

深度学习(7)--卷积神经网络项目详解

一.项目介绍&#xff1a; 用Keras工具包搭建训练自己的一个卷积神经网络(Simple_VGGNet&#xff0c;简单版VGGNet)&#xff0c;用来识别猫/狗/羊三种图片。 数据集&#xff1a; 二.卷积神经网络构造 查看API文档 Convolution layers (keras.io)https://keras.io/api/layers/…

氢气传感器报警值:守护实验室安全的隐形卫士

随着科技的发展&#xff0c;我们的生活变得越来越便捷&#xff0c;但是与此同时&#xff0c;安全问题也日益凸显。其中&#xff0c;氢气作为一种清洁能源&#xff0c;被广泛应用于各个领域&#xff0c;但是如果不加以控制&#xff0c;氢气泄漏也可能带来严重的安全隐患。因此&a…

burp靶场--xss上篇【1-15】

burp靶场–xss https://portswigger.net/web-security/cross-site-scripting 1. 什么是xss: 跨站脚本 (XSS) 是一种通常出现在 Web 应用程序中的计算机安全漏洞。XSS 允许攻击者将恶意代码注入网站&#xff0c;然后在访问该网站的任何人的浏览器中执行该代码。这可能允许攻击…

计算机设计大赛 深度学习 opencv python 实现中国交通标志识别

文章目录 0 前言1 yolov5实现中国交通标志检测2.算法原理2.1 算法简介2.2网络架构2.3 关键代码 3 数据集处理3.1 VOC格式介绍3.2 将中国交通标志检测数据集CCTSDB数据转换成VOC数据格式3.3 手动标注数据集 4 模型训练5 实现效果5.1 视频效果 6 最后 0 前言 &#x1f525; 优质…

如何将rmvb视频转换成mp4格式?如何播放rmvb视频?

RMVB文件格式的特性及使用场景 RMVB以其独特的可变比特率压缩方式而著称。这一特性使其能够根据视频内容自动调整比特率&#xff0c;不仅保证了视频质量&#xff0c;同时高效减小了文件大小。这种优势使得RMVB常见于在线视频、电视剧以及一些高清电影资源中。 RMVB文件格式的…

exec函数族和守护进程

exec函数族 进程调用exec函数族执行某个程序 进程当前内容被指定程序替换 实现让父子进程实现不同的程序: 父进程创建子进程 子进程调用exec函数族 父进程不受影响 execl和execlp #include <stdio.h> int execl (const char * path, const char * arg , ...); i…

微信小程序(二十六)列表渲染基础核心

注释很详细&#xff0c;直接上代码 上一篇 新增内容&#xff1a; 1.列表渲染基础写法 2.外部索引和自身索引 源码&#xff1a; index.wxml <view class"students"><view class"item"><text>序号</text><text>姓名</text…

大数据 - 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阶段执行过…

机器学习复习(3)——分类神经网络与drop out

完整的神经网络 以分类任务为例&#xff0c;神经网络一般包括backbone和head&#xff08;计算机视觉领域&#xff09; 下面的BasicBlock不是一个标准的backbone,标准的应该是复杂的CNNs构成的 Classfier是一个标准的head,其中output_dim表示分类类别&#xff0c;一般写作num…

【科技素养题】少儿编程 蓝桥杯青少组科技素养题真题及解析第23套

少儿编程 蓝桥杯青少组科技素养题真题及解析第23套 1、英国计算机科学家艾伦图灵于 1950 年提出了著名的“图灵测试”,用于判断计算机是否具有智能。“图灵测试”是通过()的方法进行判断的 A、让两台计算机对话 B、让人类与计算机对话 C、给计算机出题 D、让计算机分辨图…

HarmonyOS使用Web组件加载页面

1、加载网络页面 在Web组件创建时&#xff0c;指定默认加载的网络页面 。在默认页面加载完成后&#xff0c;如果开发者需要变更此Web组件显示的网络页面&#xff0c;可以通过调用loadUrl()接口加载指定的网页。 默认在Web组件加载完“www.baidu.com”页面后&#xff0c;点击按…