Skip to content

Instantly share code, notes, and snippets.

@TimoPtr
Last active December 3, 2018 14:38
Show Gist options
  • Save TimoPtr/38bc1a48626629cc214b7f9d94cca5d7 to your computer and use it in GitHub Desktop.
Save TimoPtr/38bc1a48626629cc214b7f9d94cca5d7 to your computer and use it in GitHub Desktop.
Utils to enable drag a view and scale it. It stick to the bound of the view (initial size only it doesn't handle the scale factor)
import android.view.MotionEvent
import android.view.ScaleGestureDetector
import android.view.View
fun View.enableDragAndScale() {
val scaleGestureDetector = ScaleGestureDetector(context, object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
var mScaleFactor = 1f
override fun onScale(detector: ScaleGestureDetector): Boolean {
mScaleFactor *= detector.scaleFactor
// Don't let the object get too small or too large.
mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f))
scaleX = mScaleFactor
scaleY = mScaleFactor
return false
}
})
setOnTouchListener(object : View.OnTouchListener {
var initialTouchX = 0f
var initialTouchY = 0f
var activePointerId = MotionEvent.INVALID_POINTER_ID
override fun onTouch(v: View, event: MotionEvent): Boolean {
with(event) {
//send event to the scaleDetector
scaleGestureDetector.onTouchEvent(this)
return when (actionMasked) {
MotionEvent.ACTION_DOWN -> {
actionIndex.also { pointerIndex ->
// View.X is the position into the parent referential
// rawX is the position of the click into the parent referential
// getX is the position of the click into the view referential
initialTouchX = getX(pointerIndex)
initialTouchY = getY(pointerIndex)
activePointerId = getPointerId(pointerIndex)
}
true
}
MotionEvent.ACTION_MOVE -> {
// Find the index of the active pointer and fetch its position into the view referential
val (x: Float, y: Float) = findPointerIndex(activePointerId)
.let { pointerIndex ->
// Calculate the distance moved
getX(pointerIndex) to getY(pointerIndex)
}
// compute the distance the initial touch
val dX = x - initialTouchX
val dY = y - initialTouchY
// apply some logic to stay on the boundaries of the view
val newX = (v.x + dX).let {
when {
it < 0 -> 0f // right boundary
it + v.width > rootView.width -> (v.rootView.width - v.width).toFloat() // left boundary
else -> it
}
}
val newY = (v.y + dY).let {
when {
it < 0 -> 0f // top boundary
it + v.height > v.rootView.height -> (v.rootView.height - v.height).toFloat() // bottom boundary
else -> it
}
}
// animate the translation to the new position
animate().x(newX).y(newY).setDuration(0).start()
true
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
performClick()
activePointerId = MotionEvent.INVALID_POINTER_ID
true
}
MotionEvent.ACTION_POINTER_UP -> {
actionIndex.also { pointerIndex ->
getPointerId(pointerIndex)
.takeIf { it == activePointerId }
?.run {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
val newPointerIndex = if (pointerIndex == 0) 1 else 0
initialTouchX = getX(newPointerIndex)
initialTouchY = getY(newPointerIndex)
activePointerId = newPointerIndex
}
}
true
}
else -> {
false
}
}
}
}
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment