带有悬浮窗功能的Android应用

android api29

gradle 8.9

要求

  1. 布局文件 (floating_window_layout.xml):

    • 增加、删除、关闭按钮默认隐藏。
    • 使用“开始”按钮来控制这些按钮的显示和隐藏。
  2. 服务类 (FloatingWindowService.kt):

    • 实现“开始”按钮的功能,点击时切换增加、删除、关闭按钮的可见性。
    • 处理增加、删除、关闭按钮的点击事件。
    • 使浮动窗口可拖动。
  3. 主活动 (MainActivity.kt):

    • 检查并请求悬浮窗权限。
    • 启动和停止悬浮窗服务。
  4. 清单文件 (AndroidManifest.xml):

    • 添加必要的权限声明。

floating_window_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/root_layout"android:layout_width="wrap_content"android:layout_height="wrap_content"android:orientation="vertical"android:background="#CCFFFFFF"android:padding="16dp"><Buttonandroid:id="@+id/start_button"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="开始" /><LinearLayoutandroid:id="@+id/control_buttons_layout"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"android:visibility="gone"><Buttonandroid:id="@+id/add_button"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="增加" /><Buttonandroid:id="@+id/delete_button"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="删除" /><Buttonandroid:id="@+id/close_button"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="关闭" /></LinearLayout>
</LinearLayout>

FloatingWindowService.kt

package com.example.applicationimport android.app.Service
import android.content.Intent
import android.graphics.PixelFormat
import android.os.Build
import android.os.IBinder
import android.view.Gravity
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.WindowManager
import android.widget.Button
import android.widget.LinearLayout
import android.widget.Toastclass FloatingWindowService : Service() {private var windowManager: WindowManager? = nullprivate var floatingView: View? = nullprivate var controlButtonsLayout: LinearLayout? = nulloverride fun onBind(intent: Intent?): IBinder? {return null}override fun onCreate() {super.onCreate()// 加载浮动窗口布局floatingView = LayoutInflater.from(this).inflate(R.layout.floating_window_layout, null)// 设置浮动窗口的布局参数val params = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,WindowManager.LayoutParams.WRAP_CONTENT,WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,PixelFormat.TRANSLUCENT)} else {WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,WindowManager.LayoutParams.WRAP_CONTENT,WindowManager.LayoutParams.TYPE_PHONE,WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,PixelFormat.TRANSLUCENT)}params.gravity = Gravity.TOP or Gravity.STARTparams.x = 0params.y = 100windowManager = getSystemService(WINDOW_SERVICE) as WindowManager?windowManager?.addView(floatingView, params)// 查找按钮并设置点击监听器val startButton = floatingView?.findViewById<Button>(R.id.start_button)val addButton = floatingView?.findViewById<Button>(R.id.add_button)val deleteButton = floatingView?.findViewById<Button>(R.id.delete_button)val closeButton = floatingView?.findViewById<Button>(R.id.close_button)controlButtonsLayout = floatingView?.findViewById(R.id.control_buttons_layout)startButton?.setOnClickListener {if (controlButtonsLayout?.visibility == View.VISIBLE) {controlButtonsLayout?.visibility = View.GONEstartButton.text = "开始"} else {controlButtonsLayout?.visibility = View.VISIBLEstartButton.text = "收起"}}addButton?.setOnClickListener {Toast.makeText(applicationContext, "增加", Toast.LENGTH_SHORT).show()}deleteButton?.setOnClickListener {Toast.makeText(applicationContext, "删除", Toast.LENGTH_SHORT).show()}closeButton?.setOnClickListener {stopSelf()}// 使浮动窗口可拖动val rootLayout = floatingView?.findViewById<LinearLayout>(R.id.root_layout)var initialX = 0var initialY = 0var initialTouchX = 0fvar initialTouchY = 0frootLayout?.setOnTouchListener(object : View.OnTouchListener {override fun onTouch(v: View?, event: MotionEvent?): Boolean {when (event?.action) {MotionEvent.ACTION_DOWN -> {initialX = params.xinitialY = params.yinitialTouchX = event.rawXinitialTouchY = event.rawY}MotionEvent.ACTION_MOVE -> {params.x = initialX + (event.rawX - initialTouchX).toInt()params.y = initialY + (event.rawY - initialTouchY).toInt()windowManager?.updateViewLayout(floatingView, params)}}return false}})}override fun onDestroy() {super.onDestroy()if (floatingView != null) {windowManager?.removeView(floatingView)}}
}

MainActivity.kt

package com.example.applicationimport android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Button
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.app.ActivityCompat
import com.example.application.ui.theme.ApplicationThemeclass MainActivity : ComponentActivity() {val REQUEST_CODE = 1001override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)enableEdgeToEdge()setContent {ApplicationTheme {Scaffold(modifier = Modifier.fillMaxSize()) { padding ->MainScreen(padding, LocalContext.current)}}}// 检查应用是否有权限显示悬浮窗if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) {// 请求权限以显示悬浮窗val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:$packageName"))startActivityForResult(intent, REQUEST_CODE)}}override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {super.onActivityResult(requestCode, resultCode, data)if (requestCode == REQUEST_CODE) {// 检查用户是否授予了权限if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Settings.canDrawOverlays(this)) {// 权限已授予,可以启动服务} else {// 权限未授予,显示消息或进行其他处理}}}
}@Composable
fun MainScreen(padding: PaddingValues, context: Context) {val isFloatingWindowRunning = remember { mutableStateOf(false) }Column(modifier = Modifier.fillMaxSize().padding(padding).padding(16.dp),verticalArrangement = Arrangement.Center,horizontalAlignment = Alignment.CenterHorizontally) {Greeting(name = "Android")Spacer(modifier = Modifier.height(16.dp))ToggleFloatingWindowButton(context = context,isFloatingWindowRunning = isFloatingWindowRunning.value,onToggle = {if (it) {startFloatingWindow(context)} else {stopFloatingWindow(context)}isFloatingWindowRunning.value = it})}
}@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {Text(text = "你好 $name!",modifier = modifier)
}@Preview(showBackground = true)
@Composable
fun GreetingPreview() {ApplicationTheme {Greeting("Android")}
}@Composable
fun ToggleFloatingWindowButton(context: Context,isFloatingWindowRunning: Boolean,onToggle: (Boolean) -> Unit
) {Button(onClick = {onToggle(!isFloatingWindowRunning)}) {Text(text = if (isFloatingWindowRunning) "停止悬浮窗" else "启动悬浮窗")}
}private fun startFloatingWindow(context: Context) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(context)) {// 权限未授予,再次请求val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:${context.packageName}"))ActivityCompat.startActivityForResult(context as MainActivity, intent, MainActivity().REQUEST_CODE, null)} else {val intent = Intent(context, FloatingWindowService::class.java)context.startService(intent)}
}private fun stopFloatingWindow(context: Context) {val intent = Intent(context, FloatingWindowService::class.java)context.stopService(intent)
}

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"><uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/><applicationandroid: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.Application"tools:targetApi="31"><activityandroid:name=".MainActivity"android:exported="true"android:label="@string/app_name"android:theme="@style/Theme.Application"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity><service android:name=".FloatingWindowService" /></application></manifest>
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"><uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/><applicationandroid: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.Application"tools:targetApi="31"><activityandroid:name=".MainActivity"android:exported="true"android:label="@string/app_name"android:theme="@style/Theme.Application"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity><service android:name=".FloatingWindowService" /></application></manifest>

实现了你所描述的功能:增加、删除、关闭按钮默认隐藏,并通过“开始”按钮来控制它们的显示和隐藏

 

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

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

相关文章

ML 系列:第 36 节 — 统计学中的抽样类型

ML 系列&#xff1a;第 36 天 — 统计学中的抽样类型 文章目录 一、说明二、抽样方法三、简单随机抽样四、 Stratified Sampling分层抽样五、 Cluster Sampling 整群抽样六、Systematic Sampling系统抽样七、Convenience Sampling便利抽样八、结论 一、说明 统计学中的抽样类型…

CGMA – Cloth Creation and Simulation for Real-Time

CGMA – 实时布料创建和模拟 Info&#xff1a; 本课程介绍如何将 Marvelous Designer 整合到布料工作流程中以实时创建角色&#xff0c;从软件基础知识到创建逼真和风格化服装的高级技术。本课程将首先介绍软件&#xff0c;通过创建现代、现代的服装&#xff0c;然后深入探讨使…

Springboot组合SpringSecurity安全插件基于密码的验证Demo

Springboot组合SpringSecurity安全插件基于密码的验证Demo!下面的案例&#xff0c;都是基于数据库mysql&#xff0c;用户密码&#xff0c;验证登录的策略demo。 1&#xff1b;引入maven仓库的坐标 <dependency><groupId>org.springframework.boot</groupId>…

从Full-Text Search全文检索到RAG检索增强

从Full-Text Search全文检索到RAG检索增强 时光飞逝&#xff0c;转眼间六年过去了&#xff0c;六年前铁蛋优化单表千万级数据查询性能的场景依然历历在目&#xff0c;铁蛋也从最开始做CRUD转行去了大数据平台开发&#xff0c;混迹包装开源的业务&#xff0c;机缘巧合下做了实时…

单片机学习笔记 8. 矩阵键盘按键检测

更多单片机学习笔记&#xff1a;单片机学习笔记 1. 点亮一个LED灯单片机学习笔记 2. LED灯闪烁单片机学习笔记 3. LED灯流水灯单片机学习笔记 4. 蜂鸣器滴~滴~滴~单片机学习笔记 5. 数码管静态显示单片机学习笔记 6. 数码管动态显示单片机学习笔记 7. 独立键盘 目录 0、实现的…

【AI日记】24.11.26 聚焦 kaggle 比赛

【AI论文解读】【AI知识点】【AI小项目】【AI战略思考】【AI日记】 核心工作 1 内容&#xff1a;研究 kaggle 比赛时间&#xff1a;3 小时 核心工作 2 内容&#xff1a;学习 kaggle 比赛 Titanic - Machine Learning from Disaster时间&#xff1a;4 小时备注&#xff1a;这…

排序算法2

排序算法1-CSDN博客 排序算法1中提及的是较为基础(暴力实现&#xff0c;复杂度较高)的排序算法&#xff0c;不适合于数据量较大的场景&#xff0c;比如序列长度达到1e5 接下来以蓝桥另一道题目来理解其它的排序算法 蓝桥3226 蓝桥账户中心 样例 5 1 5 9 3 7 4、快速排序 快速排…

【数据结构实战篇】用C语言实现你的私有队列

&#x1f3dd;️专栏&#xff1a;【数据结构实战篇】 &#x1f305;主页&#xff1a;f狐o狸x 在前面的文章中我们用C语言实现了栈的数据结构&#xff0c;本期内容我们将实现队列的数据结构 一、队列的概念 队列&#xff1a;只允许在一端进行插入数据操作&#xff0c;在另一端…

macos 14.0 Monoma 修改顶部菜单栏颜色

macos 14.0 设置暗色后顶部菜单栏还维持浅色&#xff0c;与整体不协调。 修改方式如下&#xff1a;

长三角文博会:Adobe国际认证体系推动设计人才评价新标准

2024年11月22日&#xff0c;由上海、江苏、浙江、安徽三省一市党委宣传部共同发起的第五届长三角文化博览会&#xff08;简称“长三角文博会”&#xff09;在上海国家会展中心盛大启幕。长三角文博会自2018年起已成功举办多届&#xff0c;已成为展示区域文化产业发展成果、推动…

安装数据库客户端工具

如果没有勾选下面的&#xff0c;可以运行下面的两个命令 红框为自带数据库 新建数据库 右键运行mysql文件&#xff0c;找到数据库&#xff0c;并刷新

SQL 复杂查询

目录 复杂查询 一、目的和要求 二、实验内容 &#xff08;1&#xff09;查询出所有水果产品的类别及详情。 查询出编号为“00000001”的消费者用户的姓名及其所下订单。&#xff08;分别采用子查询和连接方式实现&#xff09; 查询出每个订单的消费者姓名及联系方式。 在…

Angular面试题汇总系列一

1. 如何理解Angular Signal Angular Signals is a system that granularly tracks how and where your state is used throughout an application, allowing the framework to optimize rendering updates. 什么是信号 信号是一个值的包装器&#xff0c;可以在该值发生变化时…

ES 和Kibana-v2 带用户登录验证

1. 前言 ElasticSearch、可视化操作工具Kibana。如果你是Linux centos系统的话&#xff0c;下面的指令可以一路CV完成服务的部署。 2. 服务搭建 2.1. 部署ElasticSearch 拉取docker镜像 docker pull elasticsearch:7.17.21 创建挂载卷目录 mkdir /**/es-data -p mkdir /**/…

【踩坑】git中文乱码问题

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 背景说明 使用git diff显示中文乱码&#xff0c;如&#xff1a; 修复方法 执行一次&#xff1a; export LESSCHARSETutf-8 如果需要下次登录免输入…

go语言逆向-基础basic

文章目录 go 编译命令 ldflags -w -s的作用和问题使用 file 命令查看文件类型 go 语言逆向参考go ID版本GOROOT和GOPATHGOROOTGOPATHGOROOT和GOPATH的关系示例 go build和 go modpclntab &#xff08;Program Counter Line Table 程序计数器行数映射表&#xff09;Moduledata程…

D2761 适合在个人电脑、便携式音响等系统中作音频限幅用。

概述&#xff1a; D2761是为保护扬声器所设计的音频限幅器&#xff0c;其限幅值可通过外接电阻来调节&#xff0c;适合在个人电脑、便携式音响等系统中作音频限幅用。D2761采用SSOP10、MSOP10、TSSOP14的封装形式封装。 主要特点&#xff1a;  工作电压范围宽&#xff1a;2.7…

【Linux系统】—— 基本指令(四)

【Linux系统】—— 基本指令&#xff08;三&#xff09; 1「find」指令2 「grep」指令2.1 初识「grep」指令2.2 「grep」指令 选项 3 打包压缩基本知识4 「zip / unzip」指令5「tar」命令6 文件互传6.1 Linux 与 Windows 互传6.1.1 Linux向Windows传输6.1.2 Windows向Linux传输…

WordCloud去掉停用词(fit_words+generate)的2种用法

-------------词云图集合------------- WordCloud去掉停用词&#xff08;fit_wordsgenerate&#xff09;的2种用法 通过词频来绘制词云图&#xff08;jiebaWordCloud&#xff09; Python教程95&#xff1a;去掉停用词词频统计jieba.tokenize示例用法 将进酒—李白process_t…

洛谷刷题日记12||图的遍历

反向建边 dfs 按题目来每次考虑每个点可以到达点编号最大的点&#xff0c;不如考虑较大的点可以反向到达哪些点 循环从N到1&#xff0c;则每个点i能访问到的结点的A值都是i 每个点访问一次&#xff0c;这个A值就是最优的&#xff0c;因为之后如果再访问到这个结点那么答案肯…