Android 悬浮窗

本文参考文章地址:https://juejin.cn/post/7009180088310693919

一、申请权限

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

二、创建悬浮窗service

<service
    android:name=".FloatingWindowService"
    android:enabled="true"
    android:exported="true">
</service>

三、悬浮窗布局文件

<?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:background="#111111"
            android:layout_gravity="end" />
    </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>

四、悬浮窗设置

private lateinit var windowManager: WindowManager
private lateinit var layoutParams: WindowManager.LayoutParams
private lateinit var tvContent: AppCompatTextView
private var floatingView: View? = null
private val stringBuilder = StringBuilder()
private var x = 0
private var y = 0
 // 获取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
     }
     gravity = Gravity.START or Gravity.TOP
     flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
     width = 300
     height = 500
 }

if (Settings.canDrawOverlays(this)) {
    //新建悬浮窗事件
    floatingView = LayoutInflater.from(this).inflate(R.layout.layout_float_view, null)
    //文字显示控件
    tvContent = floatingView!!.findViewById(R.id.tv_content)
    //关闭悬浮窗
    floatingView!!.findViewById<AppCompatImageView>(R.id.iv_close).setOnClickListener {
        windowManager.removeView(floatingView)
    }
    // 设置TextView滚动
    tvContent.movementMethod = ScrollingMovementMethod.getInstance()
    //设置悬浮窗移动
    floatingView!!.findViewById<FrameLayout>(R.id.layout_drag).setOnTouchListener { _, 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)
}

五、通过广播联调显示悬浮窗

全部代码如下

class MainActivity : AppCompatActivity() {

    private lateinit var buttonSend: Button
    private lateinit var buttonView: Button
    private val TAG = "MainActivity"

    @SuppressLint("MissingInflatedId", "UnspecifiedRegisterReceiverFlag")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        buttonSend = findViewById(R.id.buttonClick)
        buttonSend.setOnClickListener {
            sendMessage()
        }
        buttonView = findViewById(R.id.buttonView)
        buttonView.setOnClickListener {
            startWindow()
        }
    }

    //发送广播到悬浮窗
    private fun sendMessage() {
        Intent("android.intent.action.MyReceiver").apply {
            putExtra("content", "float view test!")
            sendBroadcast(this)
        }
    }

    //检查悬浮窗权限是否打开,若没有打开则打开系统设置页面
    private fun startWindow() {
        if (!Settings.canDrawOverlays(this)) {
            startActivityForResult(
                Intent(
                    Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                    Uri.parse("package:$packageName")
                ), 0
            )
        } else {
            startService(Intent(this, 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, 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: ViewReceiver? = null
    private var floatingView: View? = null
    private val stringBuilder = StringBuilder()

    private var x = 0
    private var y = 0
    
    private var floatView = false

    @SuppressLint("UnspecifiedRegisterReceiverFlag")
    override fun onCreate() {
        super.onCreate()
        // 注册广播
        receiver = ViewReceiver()
        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
            }
            gravity = Gravity.START or Gravity.TOP
            flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
            width = 300
            height = 500
        }
        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_float_view, null)
            //文字显示控件
            tvContent = floatingView!!.findViewById(R.id.tv_content)
            //关闭悬浮窗
            floatingView!!.findViewById<AppCompatImageView>(R.id.iv_close).setOnClickListener {
                stringBuilder.clear()
                windowManager.removeView(floatingView)
                floatView = false
            }
            // 设置TextView滚动
            tvContent.movementMethod = ScrollingMovementMethod.getInstance()
            //设置悬浮窗移动
            floatingView!!.findViewById<FrameLayout>(R.id.layout_drag).setOnTouchListener { _, 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)
            floatView = true
        }
        return super.onStartCommand(intent, flags, startId)
    }

    override fun onDestroy() {
        // 注销广播并删除浮窗
        unregisterReceiver(receiver)
        receiver = null
        if (floatView) {
            windowManager.removeView(floatingView)
        }
    }

    inner class ViewReceiver : 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,android)