想要实现一个可拖动的悬浮窗,原理上非常简单:根据action_move时的event坐标偏移,去修改view的位置即可。
但是实际实现上踩了个小坑。
详细踩坑:
平时获取event的坐标,都是用event.getX() 和 event.getY() ,但是实操下来发现,这样会导致拖动效果不跟手, 拖动的位置比手势移动的位置要小,并且会有明显的位置抖动。
在这个拖动的场景下,其实应该使用 event.getRawX() 和 event.getRawY() 来获取坐标。
event.getX() 和 event.getRawX() 的区别:
/**
* {@link #getX(int)} for the first pointer index (may be an
* arbitrary pointer identifier).
*
* @see #AXIS_X
*/
public final float getX() {
return nativeGetAxisValue(mNativePtr, AXIS_X, 0, HISTORY_CURRENT);
}
/**
* Returns the original raw X coordinate of this event. For touch
* events on the screen, this is the original location of the event
* on the screen, before it had been adjusted for the containing window
* and views.
*
* @see #getX(int)
* @see #AXIS_X
*/
public final float getRawX() {
return nativeGetRawAxisValue(mNativePtr, AXIS_X, 0, HISTORY_CURRENT);
}
getRawX()使用的是相对屏幕的坐标,而 getX()使用的是相对view的坐标,在view本身随手势移动的时候,这个相对view的坐标就会发生错误的偏移,导致拖动效果不跟手。
实际代码
binding.root.setOnTouchListener(object : View.OnTouchListener {
var xDown = 0f
var yDown = 0f
var interactorTranslationXWhenDown = 0f
var interactorTranslationYWhenDown = 0f
override fun onTouch(v: View?, event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
xDown = event.rawX
yDown = event.rawY
interactorTranslationXWhenDown = interactorTranslationX
interactorTranslationYWhenDown = interactorTranslationY
}
MotionEvent.ACTION_MOVE -> {
val dx = event.rawX - xDown
val dy = event.rawY - yDown
interactorTranslationX =
interactorTranslationXWhenDown + dx
interactorTranslationY =
interactorTranslationYWhenDown + dy
Timber.e("dx:${dx} dy:${dy}")
updateTranslation()
}
else -> {}
}
return false
}
})
注:如果想写的简单一点,也可以直接使用
interactorTranslationX = event.rawX
interactorTranslationY = event.rawY
这样也能实现基本的拖动效果,只是view会跳到手指右下角的位置来拖动,有点难用。