现在很多应用都有小悬浮窗的功能,比如看直播的时候,通过Home键返回桌面,直播的小窗口仍可以在屏幕上显示。下面将介绍下悬浮窗的的一种简单实现方式。
2.原理
Window
我们应该很熟悉,它是一个接口类,具体的实现类为PhoneWindow
,它可以对View进行管理。WindowManager
是一个接口类,继承自ViewManager
,从名称就知道它是用来管理Window
的,它的实现类是WindowManagerImpl
。如果我们想要对Window(View)进行添加、更新和删除操作就可以使用WindowManager
,WindowManager
会将具体的工作交由WindowManagerService
处理。这里我们只需要知道WindowManager
能用来管理Window
就好。
WindowManager
是一个接口类,继承自ViewManager
,ViewManager
中定义了3个方法,分布用来添加、更新和删除View,如下所示:
public interface ViewManager {
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
WindowManager
也继承了这些方法,而这些方法传入的参数都是View类型,说明了Window
是以View的形式存在的。
3.具体实现
3.1浮窗布局
悬浮窗的简易布局如下的可参考下面的layout_floating_window.xml
文件。顶层深色部分的FrameLayout
布局是用来实现悬浮窗的拖拽功能的,点击右上角ImageView
可以实现关闭悬浮窗,剩下区域显示内容,这里只是简单地显示文本内容,不做复杂的东西,故只设置TextView。
<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”/>
<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”/>
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.FLAG_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(R.id.iv_close).setOnClickListener {
windowManager.removeView(floatingView)
}
// 实现浮窗的拖动功能, 通过改变layoutParams来实现
floatingView.findViewById(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
中添加:
此外,还要通过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
private lateinit var tvContent: AppCompatTextView
private lateinit var handler: Handler
private var receiver: MyReceiver? = null
private var floatingView: View? = null
private val stringBuilder = StringBuilder()
private var x = 0
private var y = 0
最后
最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上相关的我搜集整理的24套腾讯、字节跳动、阿里、百度2019-2021BAT 面试真题解析,我把大厂面试中常被问到的技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节。
还有 高级架构技术进阶脑图 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
Android 基础知识点
Java 基础知识点
Android 源码相关分析
常见的一些原理性问题
希望大家在今年一切顺利,进到自己想进的公司,共勉!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!
最后
最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上相关的我搜集整理的24套腾讯、字节跳动、阿里、百度2019-2021BAT 面试真题解析,我把大厂面试中常被问到的技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节。
还有 高级架构技术进阶脑图 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
Android 基础知识点
Java 基础知识点
Android 源码相关分析
常见的一些原理性问题
[外链图片转存中…(img-dFSnIRiF-1714550733888)]
希望大家在今年一切顺利,进到自己想进的公司,共勉!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!