在 Android 开发中,实现悬浮窗功能通常需要使用 System Alert Window 权限以及通过 WindowManager 来动态添加视图。悬浮窗是一种常见的 UI 元素,可以在应用界面上方显示,用于显示浮动的按钮、聊天小窗等内容。
-
申请权限
首先,应用需要请求 SYSTEM_ALERT_WINDOW 权限,该权限允许应用创建悬浮窗并显示在其他应用之上。
在 AndroidManifest.xml 中添加以下权限声明:<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
-
判断权限是否已获取
在 Android 6.0(API 23)及更高版本,SYSTEM_ALERT_WINDOW 权限是运行时权限,需要通过代码动态请求。你可以使用 Settings.canDrawOverlays() 方法来判断是否具有该权限if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (!Settings.canDrawOverlays(this)) { // 没有权限,提示用户去设置界面开启权限 Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())); startActivityForResult(intent, REQUEST_CODE_OVERLAY_PERMISSION); } else { // 已经获取权限,创建悬浮窗 showFloatingWindow(); } } else { // Android 6.0 以下版本,默认具有权限 showFloatingWindow(); }
-
创建悬浮窗视图
悬浮窗本质上是一个普通的 View,但它通过 WindowManager 添加到屏幕上。创建悬浮窗的步骤通常是:
3.1 使用 LayoutInflater 来加载一个布局文件。
3.2 使用 WindowManager 将该视图动态添加到屏幕上。
3.3 设置视图的位置,可以通过监听触摸事件来实现移动效果。class FloatingWindowService: Service() { private val TAG = "FloatingWindowService" private var floatingView:View?=null private var windowManager:WindowManager?=null override fun onBind(intent: Intent): IBinder { TODO("Return the communication channel to the service.") } @SuppressLint("ClickableViewAccessibility") override fun onCreate() { super.onCreate() windowManager = getSystemService(WINDOW_SERVICE) as WindowManager?; // 获取浮动窗口的布局 floatingView:View= LayoutInflater.from(this).inflate(R.layout.floating,null); // 创建LayoutParams var params = WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT,//宽 WindowManager.LayoutParams.WRAP_CONTENT,//高 // 为悬浮窗设置类型 WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT ); // 设置悬浮窗的位置 params.gravity = Gravity.LEFT params.x = 0; params.y = 0; // 添加视图到窗口 windowManager?.addView(floatingView,params); // 添加拖拽或触摸事件,允许用户拖动悬浮窗 floatingView?.setOnTouchListener(object : View.OnTouchListener { private var initialX = 0 private var initialY = 0 private var initialTouchX = 0f private var initialTouchY = 0f override fun onTouch(v: View?, event: MotionEvent): Boolean { var params = floatingView?.layoutParams as WindowManager.LayoutParams when (event.action) { MotionEvent.ACTION_DOWN -> { // 记录初始触摸位置 initialX = params.x initialY = params.y initialTouchX = event.rawX initialTouchY = event.rawY return true } MotionEvent.ACTION_MOVE -> { // 根据移动的距离更新悬浮窗的位置 params.x = initialX + (event.rawX - initialTouchX).toInt() params.y = initialY + (event.rawY - initialTouchY).toInt() // 更新视图位置 windowManager!!.updateViewLayout(floatingView, params) return true } } return false } }) } override fun onDestroy() { super.onDestroy() // 在服务销毁时移除悬浮窗 windowManager?.removeView(floatingView) } }
-
布局文件
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" android:padding="10dp" android:background="@drawable/floating_window_background"> <ImageView android:id="@+id/floating_button" android:layout_width="50dp" android:layout_height="50dp" android:src="@drawable/ic_floating_button" android:contentDescription="Floating Button" /> </LinearLayout>
-
启动悬浮窗服务
val intent = Intent(mConext, FloatingWindowService::class.java) startService(intent)