1.引言
项目中用到android悬浮窗,可是有些手机即使在悬浮窗权限授权了也无法弹出。因为项目中要对悬浮窗是否弹出做埋点。所以得给悬浮窗设置监听事件。难点在于如何设置监听事件?
2.正题
2.1实现方案
1.通过attachToWindow监听View是否被添加进Window来判断是否显示(×)
- 监听View是否能获取到焦点来判断显示(×)
3.延时200ms,获取View的visibiable状态来判断是否显示(×)
4.延时200ms,根据View判断View的OnDraw方法是否执行来判断是否显示(×)
5.获取View在屏幕中的可见区域来判断,是否显示。弹框显示,说明View被绘制出来了,被绘制出来了。说明能用 getLocalVisibleRect或者getGlobalVisibleRect获取到View在屏幕中的位置(×)
以上的方案,都试过,发现不行。最终的方案: 既然View显示出来了。我是不是可以延时200ms,发送点击事件,没有显示则无法响应点击事件;能响应点击事件,说明弹出了。
2.2悬浮窗的实现
- 权限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />
2.打开系统悬浮窗权限
private fun showFloatingWindow() {
// 获取WindowManager服务
val windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
var bl = false;
var clickFlag = false;
// 新建悬浮窗控件
val button = Button(applicationContext)
button.setText("悬浮窗A")
button.setBackgroundColor(Color.BLUE)
button.setOnClickListener {
clickFlag = true;
Toast.makeText(this, "点击悬浮窗", Toast.LENGTH_LONG).show()
}
button.postDelayed(object : Runnable {
override fun run() {
bl = true;
setSimulateClick2(300f + 20, 300f + 20);
}
}, 100)
button.postDelayed(object : Runnable {
override fun run() {
if (bl == clickFlag) {
Toast.makeText(this@MainActivity, "悬浮窗弹出", Toast.LENGTH_LONG).show()
} else {
Toast.makeText(this@MainActivity, "悬浮窗没弹出", Toast.LENGTH_LONG).show()
}
}
}, 300)
// 设置LayoutParam
val layoutParams = WindowManager.LayoutParams()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
} else {
layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE
}
layoutParams.format = PixelFormat.RGBA_8888
layoutParams.width = 500
layoutParams.height = 500
layoutParams.x = 300
layoutParams.y = 300
// 将悬浮窗控件添加到WindowManager
windowManager.addView(button, layoutParams)
}
/**
* 方式1 通过MotionEvent 发送点击事件
*/
private fun setSimulateClick(view: View, x: Float, y: Float) {
var downTime = SystemClock.uptimeMillis()
val downEvent = MotionEvent.obtain(
downTime, downTime,
MotionEvent.ACTION_DOWN, x, y, 0
)
downTime += 100
val upEvent = MotionEvent.obtain(
downTime, downTime,
MotionEvent.ACTION_UP, x, y, 0
)
view.onTouchEvent(downEvent)
view.onTouchEvent(upEvent)
downEvent.recycle()
upEvent.recycle()
}
/**
* 方式2 通过 Instrumentation发送点击事件
*/
private fun setSimulateClick2(x: Float, y: Float) {
Thread {
try {
val mInstrumentation = Instrumentation()
mInstrumentation.sendPointerSync(
MotionEvent.obtain(
SystemClock.uptimeMillis(),
SystemClock.uptimeMillis(),
MotionEvent.ACTION_DOWN,
x.toFloat(),
y.toFloat(),
0
)
) //x,y 即是事件的坐标
mInstrumentation.sendPointerSync(
MotionEvent.obtain(
SystemClock.uptimeMillis(),
SystemClock.uptimeMillis(),
MotionEvent.ACTION_UP,
x.toFloat(),
y.toFloat(),
0
)
)
} catch (e: Exception) {
e.printStackTrace()
}
}.start()
}