Android 悬浮窗的简单实现,音视频二次开发
3.具体实现
3.1 浮窗布局
悬浮窗的简易布局如下的可参考下面的layout_floating_window.xml
文件。顶层深色部分的FrameLayout
布局是用来实现悬浮窗的拖拽功能的,点击右上角ImageView
可以实现关闭悬浮窗,剩下区域显示内容,这里只是简单地显示文本内容,不做复杂的东西,故只设置 TextView。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:id="@+id/layout_drag"
android:layout_width="match_parent"
android:layout_height="15dp"
android:background="#dddddd">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_close"
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_gravity="end"
android:src="@drawable/img_delete"/>
</FrameLayout>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
android:background="#eeeeee"
android:scrollbars="vertical"/>
</LinearLayout>
3.2 悬浮窗的实现
1. 使用服务Service
Service
是一种可在后台执行长时间运行操作而不提供界面的应用组件,可由其他应用组件启动,而且即使用户切换到其他应用,仍将在后台继续运行。要保证应用在后台时,悬浮窗仍然可以正常显示,所以这里可以使用Service
。
2. 获取WindowManager
并设置LayoutParams
private lateinit var windowManager: WindowManager
private lateinit var layoutParams: WindowManager.LayoutParams
override fun onCreate() {
// 获取 WindowManager
windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
layoutParams = WindowManager.LayoutParams().apply {
// 实现在其他应用和窗口上方显示浮窗
type = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
} else {
WindowManager.LayoutParams.TYPE_PHONE
}
format = PixelFormat.RGBA_8888
// 设置浮窗的大小和位置
gravity = Gravity.START or Gravity.TOP
flags = WindowManager.LayoutParams.F
LAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
width = 600
height = 600
x = 300
y = 300
}
}
3. 创建 View 并添加到WindowManager
private lateinit var floatingView: View
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (Settings.canDrawOverlays(this)) {
floatingView = LayoutInflater.from(this).inflate(R.layout.layout_floating_window.xml, null)
windowManager.addView(floatingView, layoutParams)
}
return super.onStartCommand(intent, flags, startId)
}
4. 实现悬浮窗的拖拽和关闭功能
// 浮窗的坐标
private var x = 0
private var y = 0
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { ?
if (Settings.canDrawOverlays(this)) {
floatingView = LayoutInflater.from(this).inflate(R.layout.layout_floating_window.xml, null)
windowManager.addView(floatingView, layoutParams)
// 点击浮窗的右上角关闭按钮可以关闭浮窗
floatingView.findViewById<AppCompatImageView>(R.id.iv_close).setOnClickListener {
windowManager.removeView(floatingView)
}
// 实现浮窗的拖动功能, 通过改变 layoutParams 来实现
floatingView.findViewById<AppCompatImageView>(R.id.layout_drag).setOnTouchListener { v, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
x = event.rawX.toInt()
y = event.rawY.toInt()
}
MotionEvent.ACTION_MOVE -> {
val currentX = event.rawX.toInt()
val currentY = event.rawY.toInt()
val offsetX = currentX - x
val offsetY = currentY - y
x = currentX
y = currentY
layoutParams.x = layoutParams.x + offsetX
layoutParams.y = layoutParams.y + offsetY
// 更新 floatingView
windowManager.updateViewLayout(floatingView, layoutParams)
}
}
true
}
return super.onStartCommand(intent, flags, startId)
}
5. 利用广播进行通信
private var receiver: MyReceiver? = null
override fun onCreate() {
// 注册广播
receiver = MyReceiver()
val filter = IntentFilter()
filter.addAction("android.intent.action.MyReceiver")
registerReceiver(receiver, filter)
}
inner class MyReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val content = intent.getStringExtra("content") ?: ""
// 通过 Handler 更新 UI
val message = Message.obtain()
message.what = 0
message.obj = content
handler.sendMessage(message)
}
}
val handler = Handler(this.mainLooper) { msg ->
tvContent.text = msg.obj as String
false
}
可以在 Activity 中通过广播给 Service 发送信息
fun sendMessage(view: View?) {
Intent("android.intent.action.MyReceiver").apply {
putExtra("content", "Hello, World!")
sendBroadcast(this)
}
}
6. 设置权限
悬浮窗的显示需要权限,在AndroidManefest.xml
中添加:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
此外,还要通过Settings.ACTION_MANAGE_OVERLAY_PERMISSION
来让动态设置权限,在 Activity 中设置。
// MainActivity.kt
fun startWindow(view: View?) {
if (!Settings.canDrawOverlays(this)) {
startActivityForResult(Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:$packageName")), 0)
} else {
startService(Intent(this@MainActivity, FloatingWindowService::class.java))
}
}
?
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 0) {
if (Settings.canDrawOverlays(this)) {
Toast.makeText(this, "悬浮窗权限授权成功", Toast.LENGTH_SHORT).show()
startService(Intent(this@MainActivity, FloatingWindowService::class.java))
}
}
}
3.3 完整代码
class FloatingWindowService : Service() {
private lateinit var windowManager: WindowManager
private lateinit var layoutParams: WindowManager.LayoutParams
评论