How to manually create Android MotionEvents for a pinch gesture

41 views Asked by At

I was looking to create Android MotionEvents to simulate a pinch (scale) gesture. There wasn't any good documentation, so I am adding my solution below.

1

There are 1 answers

0
Tad On

Generating a MotionEvent requires a lot of parameters, so I created this method to simplify generating the events I needed (in my case, two pointer events moving toward or away from each other on the X axis:

private fun generateMotionEvent(animationStart: Long, centerX: Float, xOffset: Float, y: Float) : MotionEvent {
    val pointerProperties = Array(2) { PointerProperties() }
    pointerProperties[0].id = 0
    pointerProperties[1].id = 1
    val pointerCoords = Array(2) { MotionEvent.PointerCoords() }
    pointerCoords[0].x = centerX - xOffset
    pointerCoords[0].y = y
    pointerCoords[1].x = centerX + xOffset
    pointerCoords[1].y = y
    return MotionEvent.obtain(
        animationStart,             // downTime
        SystemClock.uptimeMillis(), // eventTime
        MotionEvent.ACTION_MOVE,    // action
        2, // pointerCount
        pointerProperties, // PointerProperties[] pointerProperties
        pointerCoords, // PointerCoords[] pointerCoords
        0,  // int metaState
        0,  // int buttonState
        1F, // float xPrecision
        1F, // float yPrecision
        0,  // int deviceId
        0,  // int edgeFlags
        0,  // int source
        0   // int flags
    )
}

I then needed a series of events to trigger a ScaleGestureDetector. (Two pointers down, pinching, then two pointers up) Here is a quick version of what was needed.

Pointers down:

val animationStart = SystemClock.uptimeMillis()

val leftFingerDownEvent = MotionEvent.obtain(animationStart, animationStart, MotionEvent.ACTION_DOWN,
    leftFinger.x, leftFinger.y, 0)
targetView.dispatchTouchEvent(leftFingerDownEvent)
val rightFingerDownAction = MotionEvent.ACTION_POINTER_DOWN or (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT)
val rightFingerDownEvent = generateMotionEvent(animationStart, fingerX, initialFingerSpacing.toFloat(), fingerY)
rightFingerDownEvent.action = rightFingerDownAction
targetView.dispatchTouchEvent(rightFingerDownEvent)

Motion (in my case, inside an ObjectAnimator):

val event = generateMotionEvent(animationStart, fingerX, fingerX - value, fingerY)
targetView.dispatchTouchEvent(event)

Pointers up:

val rightFingerUpAction = MotionEvent.ACTION_POINTER_UP or (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT)
val rightFingerUpEvent = generateMotionEvent(animationStart, fingerX, initialFingerSpacing.toFloat(), fingerY)
rightFingerUpEvent.action = rightFingerUpAction
audioView.dispatchTouchEvent(rightFingerUpEvent)
val leftFingerUpEvent = MotionEvent.obtain(animationStart, SystemClock.uptimeMillis(),
    MotionEvent.ACTION_UP, leftFinger.x, leftFinger.y, 0)
targetView.dispatchTouchEvent(leftFingerUpEvent)

I thought it was worth leaving this answer here for posterity, in case someone else hit a similar problem.