小编给大家分享一下Android如何实现悬浮窗,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!
现在很多应用都有小悬浮窗的功能,比如看直播的时候,通过Home键返回桌面,直播的小窗口仍可以在屏幕上显示。
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的形式存在的。
悬浮窗的简易布局如下的可参考下面的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>
Service 是一种可在后台执行长时间运行操作而不提供界面的应用组件,可由其他应用组件启动,而且即使用户切换到其他应用,仍将在后台继续运行。要保证应用在后台时,悬浮窗仍然可以正常显示,所以这里可以使用Service。
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 } }
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) }
// 浮窗的坐标 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) }
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) } }
悬浮窗的显示需要权限,在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)) } } }
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 // 用来判断floatingView是否attached 到 window manager,防止二次removeView导致崩溃 private var attached = false override fun onCreate() { super.onCreate() // 注册广播 receiver = MyReceiver() val filter = IntentFilter() filter.addAction("android.intent.action.MyReceiver") registerReceiver(receiver, filter); // 获取windowManager并设置layoutParams 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 // format = PixelFormat.TRANSPARENT 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 } handler = Handler(this.mainLooper) { msg -> tvContent.text = msg.obj as String // 当文本超出屏幕自动滚动,保证文本处于最底部 val offset = tvContent.lineCount * tvContent.lineHeight floatingView?.apply { if (offset > height) { tvContent.scrollTo(0, offset - height) } } false } } override fun onBind(intent: Intent?): IBinder? { return null } @SuppressLint("ClickableViewAccessibility") override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { if (Settings.canDrawOverlays(this)) { floatingView = LayoutInflater.from(this).inflate(R.layout.layout_show_log, null) tvContent = floatingView!!.findViewById(R.id.tv_log) floatingView!!.findViewById<AppCompatImageView>(R.id.iv_close).setOnClickListener { stringBuilder.clear() windowManager.removeView(floatingView) attached = false } // 设置TextView滚动 tvContent.movementMethod = ScrollingMovementMethod.getInstance() floatingView!!.findViewById<FrameLayout>(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 windowManager.updateViewLayout(floatingView, layoutParams) } } true } windowManager.addView(floatingView, layoutParams) attached = true } return super.onStartCommand(intent, flags, startId) } override fun onDestroy() { // 注销广播并删除浮窗 unregisterReceiver(receiver) receiver = null if (attached) { windowManager.removeView(floatingView) } } inner class MyReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val content = intent.getStringExtra("content") ?: "" stringBuilder.append(content).append("\n") val message = Message.obtain() message.what = 0 message.obj = stringBuilder.toString() handler.sendMessage(message) } } }
以上是“Android如何实现悬浮窗”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注亿速云行业资讯频道!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。