千家信息网

Android如何实现悬浮窗

发表于:2025-01-19 作者:千家信息网编辑
千家信息网最后更新 2025年01月19日,小编给大家分享一下Android如何实现悬浮窗,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!1. 前言现在很多应用都有小悬
千家信息网最后更新 2025年01月19日Android如何实现悬浮窗

小编给大家分享一下Android如何实现悬浮窗,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!

    1. 前言

    现在很多应用都有小悬浮窗的功能,比如看直播的时候,通过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。

                        

    3.2 悬浮窗的实现

    1. 使用服务Service

    Service 是一种可在后台执行长时间运行操作而不提供界面的应用组件,可由其他应用组件启动,而且即使用户切换到其他应用,仍将在后台继续运行。要保证应用在后台时,悬浮窗仍然可以正常显示,所以这里可以使用Service。

    2. 获取WindowManager并设置LayoutParams
    private lateinit var windowManager: WindowManagerprivate lateinit var layoutParams: WindowManager.LayoutParamsoverride 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: Viewoverride 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 = 0private var y = 0override 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? = nulloverride 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.ktfun 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    privateval 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(R.id.iv_close).setOnClickListener {                stringBuilder.clear()                windowManager.removeView(floatingView)                attached = false            }            // 设置TextView滚动            tvContent.movementMethod = ScrollingMovementMethod.getInstance()            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                        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如何实现悬浮窗"这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注行业资讯频道!

    0