android api29
gradle 8.9
要求
-
布局文件 (
floating_window_layout.xml
):- 增加、删除、关闭按钮默认隐藏。
- 使用“开始”按钮来控制这些按钮的显示和隐藏。
-
服务类 (
FloatingWindowService.kt
):- 实现“开始”按钮的功能,点击时切换增加、删除、关闭按钮的可见性。
- 处理增加、删除、关闭按钮的点击事件。
- 使浮动窗口可拖动。
-
主活动 (
MainActivity.kt
):- 检查并请求悬浮窗权限。
- 启动和停止悬浮窗服务。
-
清单文件 (
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>
实现了你所描述的功能:增加、删除、关闭按钮默认隐藏,并通过“开始”按钮来控制它们的显示和隐藏