Android 实现悬浮球的功能

news2025/6/3 23:12:11

Android 实现悬浮球的功能

在 Android 中,实现悬浮球可以通过以下方式实现,常见的方法是使用 WindowManager 创建一个悬浮窗口。以下是具体的实现步骤:

1. 配置权限

AndroidManifest.xml 中添加悬浮窗权限:

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

对于 Android 6.0 及以上版本,还需要动态申请悬浮窗权限。

AndroidManifest.xml 文件如下

 
<?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
     package="com.check.floatingball">
 ​
     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
 ​
 ​
     <application
         android:allowBackup="true"
         android:dataExtractionRules="@xml/data_extraction_rules"
         android:fullBackupContent="@xml/backup_rules"
         android:icon="@mipmap/ic_launcher"
         android:label="@string/app_name"
         android:roundIcon="@mipmap/ic_launcher_round"
         android:supportsRtl="true"
         android:theme="@style/Theme.FloatingBall"
         tools:targetApi="31">
         <activity
             android:name=".MainActivity"
             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
 ​
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
 ​
         <service
             android:name=".FloatingBallService"
             android:exported="false"
             android:permission="com.example.permission.ACCESS_FLOATING_BALL" />
     </application>
 ​
 </manifest>

2. 创建悬浮球服务

悬浮球通常在一个 Service 中实现,以便在后台运行。

创建一个 Service 类
 
import android.app.Service
 import android.content.Intent
 import android.os.IBinder
 import android.graphics.PixelFormat
 import android.os.Build
 import android.util.Log
 import android.view.*
 import android.widget.ImageView
 import android.widget.Toast
 ​
 class FloatingBallService : Service() {
 ​
     private lateinit var windowManager: WindowManager
     private lateinit var floatingView: ViewGroup
     private lateinit var layoutParams: WindowManager.LayoutParams
 ​
     override fun onCreate() {
         super.onCreate()
 ​
         // 初始化 WindowManager
         windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
 ​
         // 加载悬浮球布局
         floatingView = LayoutInflater.from(this).inflate(R.layout.floating_ball, null) as ViewGroup
 ​
         // 悬浮窗参数配置
         layoutParams = WindowManager.LayoutParams(
             WindowManager.LayoutParams.WRAP_CONTENT,
             WindowManager.LayoutParams.WRAP_CONTENT,
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
                 WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
             else
                 WindowManager.LayoutParams.TYPE_PHONE,
             WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
             PixelFormat.TRANSLUCENT
         )
 ​
         layoutParams.gravity = Gravity.TOP or Gravity.START
         // 第一次显示的坐标位置
         layoutParams.x = 0
         layoutParams.y = 100
 ​
         // 添加悬浮窗
         windowManager.addView(floatingView, layoutParams)
 ​
         // 添加触摸和点击事件
         val floatingIcon = floatingView.findViewById<ImageView>(R.id.floating_icon)
         // 添加onTouchListener
         floatingIcon.setOnTouchListener(FloatingBallTouchListener())
         // 添加onClickListener
         floatingIcon.setOnClickListener {
             Log.d("FloatingBall", "点击事件触发")
             Toast.makeText(this, "点击悬浮球", Toast.LENGTH_SHORT).show()
         }
     }
 ​
     override fun onDestroy() {
         super.onDestroy()
         // 移除悬浮球
         windowManager.removeView(floatingView)
     }
 ​
     override fun onBind(intent: Intent?): IBinder? {
         return null
     }
 ​
     /*
     **自定义触摸事件监听器
     */
     private inner class FloatingBallTouchListener : View.OnTouchListener {
         private var initialX = 0
         private var initialY = 0
         private var initialTouchX = 0f
         private var initialTouchY = 0f
 ​
         override fun onTouch(view: View, event: MotionEvent): Boolean {
             when (event.action) {
                 MotionEvent.ACTION_DOWN -> {
                     initialX = layoutParams.x
                     initialY = layoutParams.y
                     initialTouchX = event.rawX
                     initialTouchY = event.rawY
                     return false
                 }
                 MotionEvent.ACTION_MOVE -> {
                     layoutParams.x = initialX + (event.rawX - initialTouchX).toInt()
                     layoutParams.y = initialY + (event.rawY - initialTouchY).toInt()
                     windowManager.updateViewLayout(floatingView, layoutParams)
                     return false
                 }
             }
             return false
         }
     }
 }

3. 悬浮球布局

res/layout/floating_ball.xml 中创建悬浮球的布局文件:

 <?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/icon_root_view_group"
     android:layout_width="50dp"
     android:layout_height="50dp"
     android:background="@color/white">
 ​
     <ImageView
         android:id="@+id/floating_icon"
         android:layout_width="50dp"
         android:layout_height="50dp"
         android:src="@drawable/float_circle_transparent"
         android:contentDescription="图片" />
 </LinearLayout>

4. 添加动态权限申请

在MainActivity 中申请悬浮窗权限:

 
import androidx.appcompat.app.AppCompatActivity
 import android.os.Bundle
 ​
 import android.content.Intent
 import android.net.Uri
 import android.os.Build
 import android.provider.Settings
 import android.widget.Button
 import android.widget.Toast
 ​
 class MainActivity : AppCompatActivity() {
 ​
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         setContentView(R.layout.activity_main)
 ​
         val startButton = findViewById<Button>(R.id.start_floating_ball)
         startButton.setOnClickListener {
             if (checkOverlayPermission()) {
                 startFloatingBallService()
             } else {
                 requestOverlayPermission()
             }
         }
     }
 ​
     private fun checkOverlayPermission(): Boolean {
         return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
             Settings.canDrawOverlays(this)
         } else {
             true
         }
     }
 ​
     private fun requestOverlayPermission() {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
             val intent = Intent(
                 Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                 Uri.parse("package:$packageName")
             )
             startActivityForResult(intent, 100)
         }
     }
 ​
     private fun startFloatingBallService() {
         val intent = Intent(this, FloatingBallService::class.java)
         startService(intent)
     }
 ​
     override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
         super.onActivityResult(requestCode, resultCode, data)
         if (requestCode == 100) {
             if (checkOverlayPermission()) {
                 startFloatingBallService()
             } else {
                 Toast.makeText(this, "悬浮窗权限未授予", Toast.LENGTH_SHORT).show()
             }
         }
     }
 }

5. activity_main.xml布局


 <?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:gravity="center"
     android:orientation="vertical">
 ​
     <Button
         android:id="@+id/start_floating_ball"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:text="启动悬浮球" />
 </LinearLayout>

6. 测试运行

  1. 启动应用,点击 "启动悬浮球" 按钮。

  2. 如果未授予权限,应用会跳转到悬浮窗权限设置页面。

  3. 授予权限后,悬浮球会显示在屏幕上,可以拖动和点击。

通过以上步骤,你已经从零实现了一个基本的 Android 悬浮球功能!

点击按钮能出现下面

image-20241124222515708

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C语言数据结构学习:循环队列

C语言 数据结构学习 汇总入口&#xff1a; C语言数据结构学习&#xff1a;[汇总] 1. 循环队列 队列的博客&#xff1a;C语言数据结构学习&#xff1a;队列 循环队列会预先定义最大队列空间&#xff0c;然后定义一个数组&#xff0c;通过队列头和队列尾指针分别指向开头和结尾&…

Java教程:SE进阶【十万字详解】(下)

✨博客主页&#xff1a; https://blog.csdn.net/m0_63815035?typeblog &#x1f497;《博客内容》&#xff1a;.NET、Java.测试开发、Python、Android、Go、Node、Android前端小程序等相关领域知识 &#x1f4e2;博客专栏&#xff1a; https://blog.csdn.net/m0_63815035/cat…

37_U-Net网络详解

1.U-Net 网络概述 U-Net 是一种深度学习模型&#xff0c;广泛用于图像的语义分割任务。U-Net 网络的结构特别适合医学影像分割&#xff0c;尤其在少量训练数据的情况下表现优异。该网络由一个编码器-解码器架构组成&#xff0c;具有对称的“U”形结构&#xff0c;因此得名为 U…

mysql-分析MVCC原理

一、MVCC简介 MVCC是一种用来解决读写冲读的无锁并发控制&#xff0c;也就是为事务分配单增长的时间戳&#xff0c;为每个修改保存一个版本&#xff0c;版本与事务时间戳关联&#xff0c;读操作只读该事务开始前的数据库的快照&#xff0c;所以MVCC可以为数据库解决一些问题。…

【CSP CCF记录】201812-2第15次认证 小明放学

题目 样例1输入 30 3 30 8 0 10 1 5 0 11 2 2 0 6 0 3 3 10 0 3 样例1输出 30 3 30 8 0 10 1 5 0 11 2 2 0 6 0 3 3 10 0 3 思路 参考&#xff1a;CCF小白刷题之路---201812-2 小明放学&#xff08;C/C 100分&#xff09;_小明放学测试数据-CSDN博客 我们使用一个for循环计算…

Kafka 分区分配及再平衡策略深度解析与消费者事务和数据积压的简单介绍

Kafka&#xff1a;分布式消息系统的核心原理与安装部署-CSDN博客 自定义 Kafka 脚本 kf-use.sh 的解析与功能与应用示例-CSDN博客 Kafka 生产者全面解析&#xff1a;从基础原理到高级实践-CSDN博客 Kafka 生产者优化与数据处理经验-CSDN博客 Kafka 工作流程解析&#xff1a…

计算机网络-VPN虚拟专用网络概述

前面我们学习了在企业内部的二层交换机网络、三层路由网络包括静态路由、OSPF、IS-IS、NAT等&#xff0c;现在开始学习下VPN&#xff08;Virtual Private Network&#xff0c;虚拟专用网络&#xff09;&#xff0c;其实VPN可能很多人听到第一反应就是梯子&#xff0c;但是其实这…

《第十部分》1.STM32之通信接口《精讲》之IIC通信---介绍

经过近一周的USART学习&#xff0c;我深刻体会到通信对单片机的重要性。它就像人类的手脚和大脑&#xff0c;只有掌握了通信技术&#xff0c;单片机才能与外界交互&#xff0c;展现出丰富多彩的功能&#xff0c;变得更加强大和实用。 单片机最基础的“语言”是二进制。可惜&am…

【蓝桥杯C/C++】深入解析I/O高效性能优化:std::ios::sync_with_stdio(false)

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: 蓝桥杯C/C 文章目录 &#x1f4af;前言&#x1f4af;C 语言与 C 语言的输入输出对比1.1 C 语言的输入输出1.2 C 语言的输入输出 &#x1f4af; std::ios::sync_with_stdio(false) 的作用与意义2.1 什么是 std::ios::s…

初识Linux—— 基本指令(下)

前言&#xff1a; 本篇继续来学习Linux的基础指令&#xff0c;继续加油&#xff01;&#xff01;&#xff01; 本篇文章对于图片即内容详解&#xff0c;已同步到本人gitee&#xff1a;Linux学习: Linux学习与知识讲解 Linux指令 1、查看文件内容的指令 cat ​ cat 查看文件…

VM虚拟机装MAC后无法联网,如何解决?

✨在vm虚拟机上&#xff0c;给虚拟机MacOS设置网络适配器。选择NAT模式用于共享主机的IP地址 ✨在MacOS设置中设置网络 以太网 使用DHCP ✨回到本地电脑上&#xff0c;打开 服务&#xff0c;找到VMware DHCP和VMware NAT&#xff0c;把这两个服务打开&#xff0c;专一般问题就…

MCGSMCGS昆仑通态触摸屏

MCGS昆仑通态触摸屏应用实例详解 1目录设置 本案例讲了两个窗口的互相调用 创建工程 首先创建一个新工程 打开软件 McgsPro组态软件 菜单栏&#xff1a;文件&#xff1a;新建工程 打开工程设置窗口 HMI配置中应该是对应的不同型号的触摸屏&#xff0c; 选择一个类型&#x…

aws ses生产环境申请

* aws ses生产环境申请经验&#xff1a; 要有域名邮箱作为反馈联系邮箱 且有收发记录 最好使用aws的WorkMail要说明清晰的使用用途、预估量、如何处理退信和投诉、防spam策略 等内容&#xff0c;这里可以先问问AI&#xff08;比如&#xff1a;如何处理退信和投诉&#xff1f;…

MongoDB相关问题

视频教程 【GeekHour】20分钟掌握MongoDB Complete MongoDB Tutorial by Net Ninja MongoDB开机后调用缓慢的原因及解决方法 问题分析&#xff1a; MongoDB开机后调用缓慢&#xff0c;通常是由于以下原因导致&#xff1a; 索引重建&#xff1a; MongoDB在启动时会重建索引…

pytest日志总结

pytest日志分为两类&#xff1a; 一、终端&#xff08;控制台&#xff09;打印的日志 1、指定-s&#xff0c;脚本中print打印出的信息会显示在终端&#xff1b; 2、pytest打印的summary信息&#xff0c;这部分是pytest 的默认输出&#xff08;例如测试结果PASSED, FAILED, S…

mysql系列1—mysql架构和协议介绍

背景&#xff1a; 本文开始整理mysql相关的文章&#xff0c;用于收集数据库相关内容&#xff1b;包括mysql架构和存储方式、索引结构和查询优化、数据库锁等内容。思考如何根据具体的业务给出最优的分表规划和表设计、字段选择和索引设计、优化的SQL语句&#xff0c;以及数据库…

Opencv+ROS实现摄像头读取处理画面信息

一、工具 ubuntu18.04 ROSopencv2 编译器&#xff1a;Visual Studio Code 二、原理 图像信息 ROS数据形式&#xff1a;sensor_msgs::Image OpenCV数据形式&#xff1a;cv:Mat 通过cv_bridge()函数进行ROS向opencv转换 cv_bridge是在ROS图像消息和OpenCV图像之间进行转…

docker容器化部署springboot项目

前言 docker安装 下载官网 选择自己的系统 然后安装文档内给的命令按顺序执行即可。设置仓库&#xff0c;安装docker. 一、更换镜像源 一般情况下,docker原本自带的镜像网站不一定连的上,就很容易导致下载镜像失败,因此需要换源. 创建/etc/docker/daemon.json并填入数据…

实时数据开发|简单理解Flink流计算中解决乱序的机制--水位线

今天继续学习Flink的关键机制–水位线&#xff0c;虽然看文字有种浮于表面、难以理解的感觉&#xff0c;但是我觉得等开发中使用到的时候就会融会贯通了。 定义 Fink 相比其他流计算技术的一个重要特性是支持基于事件时间(event time)的窗口操作。但是事件时间来自于源头系统…

Edify 3D: Scalable High-Quality 3D Asset Generation 论文解读

目录 一、概述 二、相关工作 1、三维资产生成 2、多视图下的三维重建 3、纹理和材质生成 三、Edify 3D 1、文本生成多视角图像的扩散模型 2、文本和多视角图像生成法线图像的ControlNet 3、重建与渲染模型 4、多视角高分辨率RGB图像生成 四、训练 1、训练过程 2、…