I use the following code to drag a view around the screen, and it works. But, when the user first touches moveIcon
, the floatingView
suddenly moves to the center of the screen, even though I want it to remain in its position. How can I fix this? I suspect the problem is in the updatePosition() method.
windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
val layoutInflater = LayoutInflater.from(this)
floatingView = layoutInflater.inflate(R.layout.floating_layout, null)
// Set up the layout parameters for the floating view
val params = WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
} else {
@Suppress("DEPRECATION")
WindowManager.LayoutParams.TYPE_PHONE
},
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT
)
windowManager!!.addView(floatingView, params)
// Moving the views:
moveIcon = floatingView!!.findViewById(R.id.moveIcon)
moveIcon.setOnTouchListener { view, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
// Save the initial touch coordinates relative to the moveIcon view
initialTouchX = event.rawX - view.x
initialTouchY = event.rawY - view.y
}
MotionEvent.ACTION_MOVE -> {
// Calculate the new position based on the movement and initial touch coordinates
val newX = event.rawX - initialTouchX
val newY = event.rawY - initialTouchY
updatePosition(newX.toInt(), newY.toInt())
}
}
true
}
}
private fun updatePosition(x: Int, y: Int) {
val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
val layoutParams = floatingView!!.layoutParams as WindowManager.LayoutParams
layoutParams.x = x
layoutParams.y = y
windowManager.updateViewLayout(floatingView, layoutParams)
}
The X/Y Coordinates of a view are defined in documentation as the translationX/Y
plus the current left/top
property. The Left/Top position of a view are relative to its parent.
But, your floatingView
is added directly through the WindowManger
; and its parent here would be the ViewRootImpl
. So, you'd not expect that the left/top position of that view has the true values you need because ViewRootImpl
is the system wide root View that is not accessible to developers; so, if you logged down the view.x/y
you'll see them always 0
.
Instead of accessing ViewRootImpl
from the Views directly, they offered the WindowManger
API for that purpose. I.e., when the floatingView
added to the ViewRootImpl
; we didn't do ViewRootImpl.addView()
, but we did windowManager.addView()
.
Similarly, we need to get the x/y parameters through the WindowManager
as well using the WindowManager.LayoutParams
to get the right values:
....
MotionEvent.ACTION_DOWN -> {
// initialTouchX = event.rawX - view.x // Remove this
// initialTouchY = event.rawY - view.y // Remove this
(floatingView.layoutParams as WindowManager.LayoutParams).let { params ->
initialTouchX = event.rawX - params.x
initialTouchY = event.rawY - params.y
}
}