Last active
December 3, 2018 14:38
-
-
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)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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