Android 悬浮窗
本文参考文章地址:https://juejin.cn/post/7009180088310693919
一、申请权限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
二、创建悬浮窗service
<serviceandroid: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"><FrameLayoutandroid:id="@+id/layout_drag"android:layout_width="match_parent"android:layout_height="15dp"android:background="#dddddd"><androidx.appcompat.widget.AppCompatImageViewandroid:id="@+id/iv_close"android:layout_width="15dp"android:layout_height="15dp"android:background="#111111"android:layout_gravity="end" /></FrameLayout><androidx.appcompat.widget.AppCompatTextViewandroid: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并设置layoutParamswindowManager = getSystemService(WINDOW_SERVICE) as WindowManagerlayoutParams = 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.TOPflags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLEwidth = 300height = 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 - xval offsetY = currentY - yx = currentXy = currentYlayoutParams.x = layoutParams.x + offsetXlayoutParams.y = layoutParams.y + offsetYwindowManager.updateViewLayout(floatingView, layoutParams)}}true}windowManager.addView(floatingView, layoutParams)
}
五、通过广播联调显示悬浮窗
全部代码如下
class MainActivity : AppCompatActivity() {private lateinit var buttonSend: Buttonprivate lateinit var buttonView: Buttonprivate 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: WindowManagerprivate lateinit var layoutParams: WindowManager.LayoutParamsprivate lateinit var tvContent: AppCompatTextViewprivate lateinit var handler: Handlerprivate var receiver: ViewReceiver? = nullprivate var floatingView: View? = nullprivate val stringBuilder = StringBuilder()private var x = 0private var y = 0private 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并设置layoutParamswindowManager = getSystemService(WINDOW_SERVICE) as WindowManagerlayoutParams = 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.TOPflags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLEwidth = 300height = 500}handler = Handler(this.mainLooper) { msg ->tvContent.text = msg.obj as String// 当文本超出屏幕自动滚动,保证文本处于最底部val offset = tvContent.lineCount * tvContent.lineHeightfloatingView?.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 - xval offsetY = currentY - yx = currentXy = currentYlayoutParams.x = layoutParams.x + offsetXlayoutParams.y = layoutParams.y + offsetYwindowManager.updateViewLayout(floatingView, layoutParams)}}true}windowManager.addView(floatingView, layoutParams)floatView = true}return super.onStartCommand(intent, flags, startId)}override fun onDestroy() {// 注销广播并删除浮窗unregisterReceiver(receiver)receiver = nullif (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 = 0message.obj = stringBuilder.toString()handler.sendMessage(message)}}
}