带有悬浮窗功能的Android应用

ops/2024/11/28 6:35:04/

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.ppmy.cn/ops/137280.html

相关文章

新手学操作系统(第十一周)

1.与用户/组相关的一些命令 uptime <- 登录用户数&#xff0c;运行时间&#xff0c;平均负荷 who <-登录用户 whoami <-当前用户 id <-当前用户的信息 id 用户名 <- 用户名对应的用户信息 groups <-当前用户所属的组 groups 用户名 <- 用…

探寻嵌入式系统的发展之路与趋势展望

目录 一、嵌入式系统的发展历程 1.1. 早期阶段&#xff08;20世纪40年代至70年代初&#xff09; 1.1.1. 起源与背景 1.1.2. 特点 1.1.3. 应用领域 1.1.4. 发展里程碑 1.2. 单片机时代&#xff08;20世纪70年代初至80年代末&#xff09; 1.2.1. 硬件 1.2.2. 软件 1.2.…

Day47 | 动态规划 :线性DP 最长公共子序列最长公共子数组

Day47 | 动态规划 &#xff1a;线性DP 最长公共子序列&&最长公共子数组 动态规划应该如何学习&#xff1f;-CSDN博客 本次题解参考自灵神的做法&#xff0c;大家也多多支持灵神的题解 最长公共子序列 编辑距离_哔哩哔哩_bilibili 动态规划学习&#xff1a; 1.思考…

<项目代码>YOLOv8 红绿灯识别<目标检测>

YOLOv8是一种单阶段&#xff08;one-stage&#xff09;检测算法&#xff0c;它将目标检测问题转化为一个回归问题&#xff0c;能够在一次前向传播过程中同时完成目标的分类和定位任务。相较于两阶段检测算法&#xff08;如Faster R-CNN&#xff09;&#xff0c;YOLOv8具有更高的…

第三十三章 UDP 客户端 服务器通信 - IPv4 和 IPv6

文章目录 第三十三章 UDP 客户端 服务器通信 - IPv4 和 IPv6 第三十三章 UDP 客户端 服务器通信 - IPv4 和 IPv6 UDP 支持 IPv4 和 IPv6 互联网协议。由于这些协议不兼容&#xff0c;服务器和客户端都必须使用相同的Internet协议&#xff0c;否则传输将失败。 IPv4 地址具有以…

NExT-GPT: Any-to-Any Multimodal LLM

NExT-GPT: Any-to-Any Multimodal LLM ICML 2024 Oral 整体框架 Motivation 大多数多模态模型只关注输入端的多模态理解部分模型有训练输出图片和文本交互的LLM现有的any-to-any LLM存在一定的问题&#xff1a; 不同模块之间的信息传递完全基于LLM产生的离散文本&#xff0c;级…

w058基于web的美发门店管理系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以查看文章末尾⬇️联系方式获取&#xff0c;记得注明来意哦~&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0…

d3-contour 生成等高线图

D3.js 是一个强大的 JavaScript 库&#xff0c;用于创建动态、交互式数据可视化。d3-contour 是 D3.js 的一个扩展模块&#xff0c;用于生成等高线图&#xff08;contour plots&#xff09;。 属性和方法 属性 x: 一个函数&#xff0c;用于从数据点中提取 x 坐标。y: 一个函…