Android PopupWindow Y定位异常:showAtLocation偏移且滚动后错位
PopupWindow 点击位置定位异常修复方案
核心问题根源
- 坐标系不匹配导致Y坐标偏高:在
dispatchTouchEvent中获取的event.y是触摸点相对于接收触摸事件的View自身的相对坐标,不是showAtLocation接口要求的、相对于传入的mainView参考系的偏移量。你观察到X坐标正常,是因为通常页面内容的左侧位置与屏幕左侧无偏移,对应坐标值刚好匹配参考系,所以没有出现偏差。 - 滚动时错位到屏幕底部的原因是坐标计算没有动态适配:
mainView发生滚动后,它在屏幕上的绝对位置会发生变化,沿用旧的坐标值计算偏移时,一旦传入的坐标值超出参考系的有效范围,系统会自动将PopupWindow裁剪到屏幕边界,最终出现弹窗错位到屏幕最底部的问题。
分步修复操作
第一,替换触摸坐标的获取方式,用不受View布局、滚动影响的绝对坐标
去掉原来用event.y赋值的逻辑,改用event.rawY/rawX获取触摸点相对于物理屏幕左上角的绝对坐标,只在手指按下时记录坐标,避免滑动过程中坐标漂移:override fun dispatchTouchEvent(event: MotionEvent): Boolean { if (event.action == MotionEvent.ACTION_DOWN) { // 取屏幕绝对坐标,不受任何View布局位置影响 posX = event.rawX.toInt() posY = event.rawY.toInt() } return super.dispatchTouchEvent(event) }第二,做坐标系换算,抵消参考系的固定偏移
showAtLocation(mainView, Gravity.TOP | Gravity.START, x, y)的坐标原点是mainView自身的左上角位置,而我们拿到的rawY是从屏幕最顶部(包含系统状态栏区域)开始计算的,每次弹窗前实时获取mainView的屏幕位置,不要提前缓存,不然滚动后mainView的位置变化会导致计算失效。
具体换算逻辑:// 每次弹窗前实时获取mainView的屏幕位置 val mainViewOnScreenPos = IntArray(2) mainView.getLocationOnScreen(mainViewOnScreenPos) val mainViewTopY = mainViewOnScreenPos[1] // 换算得到相对于mainView的Y偏移 val correctYOffset = posY - mainViewTopY把换算后的偏移传入接口,替换原来的posY参数即可:
popupWindow.showAtLocation( mainView, Gravity.TOP or Gravity.START, posX, correctYOffset )第三,增加边界保护,避免系统裁剪错位
对计算得到的Y偏移做范围限制,避免传入的数值过大超出mainView的高度范围,触发系统的边界自动调整逻辑:// 保证偏移值在mainView的有效高度范围内 val maxValidY = mainView.height - popupWindow.height val finalYOffset = correctYOffset.coerceIn(0, maxValidY)滚动场景下,只要每次弹窗前都重新执行上面的换算逻辑,就不会出现弹窗错位的问题。
内容的提问来源于stack exchange,提问作者Alex Alex
相关产品推荐
相关产品推荐

