lightbulb_outline Please take our October 2018 developer survey. Start survey

Track touch and pointer movements

This lesson describes how to track movement in touch events.

A new onTouchEvent() is triggered with an ACTION_MOVE event whenever the current touch contact position, pressure, or size changes. As described in Detecting Common Gestures, all of these events are recorded in the MotionEvent parameter of onTouchEvent().

Because finger-based touch isn't always the most precise form of interaction, detecting touch events is often based more on movement than on simple contact. To help apps distinguish between movement-based gestures (such as a swipe) and non-movement gestures (such as a single tap), Android includes the notion of "touch slop". Touch slop refers to the distance in pixels a user's touch can wander before the gesture is interpreted as a movement-based gesture. For more discussion of this topic, see Managing Touch Events in a ViewGroup.

There are several different ways to track movement in a gesture, depending on the needs of your application. For example:

  • The starting and ending position of a pointer (for example, move an on-screen object from point A to point B).
  • The direction the pointer is traveling in, as determined by the x and y coordinates.
  • History. You can find the size of a gesture's history by calling the MotionEvent method getHistorySize(). You can then obtain the positions, sizes, time, and pressures of each of the historical events by using the motion event's getHistorical<Value> methods. History is useful when rendering a trail of the user's finger, such as for touch drawing. See the MotionEvent reference for details.
  • The velocity of the pointer as it moves across the touch screen.

Refer to the following related resources:

Track velocity

You could have a movement-based gesture that is simply based on the distance and/or direction the pointer traveled. But velocity often is a determining factor in tracking a gesture's characteristics or even deciding whether the gesture occurred. To make velocity calculation easier, Android provides the VelocityTracker class. VelocityTracker helps you track the velocity of touch events. This is useful for gestures in which velocity is part of the criteria for the gesture, such as a fling.

Here is a simple example that illustrates the purpose of the methods in the VelocityTracker API:

Kotlin

private const val DEBUG_TAG = &quot;Velocity&quot;</p>

<p>class MainActivity : Activity() {
    private var mVelocityTracker: VelocityTracker? = null</p>
<pre class="prettyprint"><code>override fun onTouchEvent(event: MotionEvent): Boolean {

    when (event.actionMasked) {
        MotionEvent.ACTION_DOWN -&gt; {
            // Reset the velocity tracker back to its initial state.
            mVelocityTracker?.clear()
            // If necessary retrieve a new VelocityTracker object to watch the
            // velocity of a motion.
            mVelocityTracker = mVelocityTracker ?: VelocityTracker.obtain()
            // Add a user&#39;s movement to the tracker.
            mVelocityTracker?.addMovement(event)
        }
        MotionEvent.ACTION_MOVE -&gt; {
            mVelocityTracker?.apply {
                val pointerId: Int = event.getPointerId(event.actionIndex)
                addMovement(event)
                // When you want to determine the velocity, call
                // computeCurrentVelocity(). Then call getXVelocity()
                // and getYVelocity() to retrieve the velocity for each pointer ID.
                computeCurrentVelocity(1000)
                // Log velocity of pixels per second
                // Best practice to use VelocityTrackerCompat where possible.
                Log.d(&#34;&#34;, &#34;X velocity: ${getXVelocity(pointerId)}&#34;)
                Log.d(&#34;&#34;, &#34;Y velocity: ${getYVelocity(pointerId)}&#34;)
            }
        }
        MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -&gt; {
            // Return a VelocityTracker object back to be re-used by others.
            mVelocityTracker?.recycle()
            mVelocityTracker = null
        }
    }
    return true
}
</code></pre>
<p>}

Java

public class MainActivity extends Activity {
    private static final String DEBUG_TAG = &quot;Velocity&quot;;
        ...
    private VelocityTracker mVelocityTracker = null;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int index = event.getActionIndex();
        int action = event.getActionMasked();
        int pointerId = event.getPointerId(index);</p>
<pre class="prettyprint"><code>    switch(action) {
        case MotionEvent.ACTION_DOWN:
            if(mVelocityTracker == null) {
                // Retrieve a new VelocityTracker object to watch the
                // velocity of a motion.
                mVelocityTracker = VelocityTracker.obtain();
            }
            else {
                // Reset the velocity tracker back to its initial state.
                mVelocityTracker.clear();
            }
            // Add a user&#39;s movement to the tracker.
            mVelocityTracker.addMovement(event);
            break;
        case MotionEvent.ACTION_MOVE:
            mVelocityTracker.addMovement(event);
            // When you want to determine the velocity, call
            // computeCurrentVelocity(). Then call getXVelocity()
            // and getYVelocity() to retrieve the velocity for each pointer ID.
            mVelocityTracker.computeCurrentVelocity(1000);
            // Log velocity of pixels per second
            // Best practice to use VelocityTrackerCompat where possible.
            Log.d(&#34;&#34;, &#34;X velocity: &#34; + mVelocityTracker.getXVelocity(pointerId));
            Log.d(&#34;&#34;, &#34;Y velocity: &#34; + mVelocityTracker.getYVelocity(pointerId));
            break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            // Return a VelocityTracker object back to be re-used by others.
            mVelocityTracker.recycle();
            break;
    }
    return true;
}
</code></pre>
<p>}

Use pointer capture

Some apps, such as games, remote desktop, and virtualization clients, greatly benefit from getting control over the mouse pointer. Pointer capture is a feature available in Android 8.0 (API level 26) and later that provides such control by delivering all mouse events to a focused view in your app.

Request pointer capture

A view in your app can request pointer capture only when the view hierarchy that contains it has focus. For this reason, you should request pointer capture when there's a specific user action on the view, such as during an onClick() event, or in the onWindowFocusChanged() event handler of your activity.

To request pointer capture, call the requestPointerCapture() method on the view. The following code example shows how to request pointer capture when the user clicks a view:

Kotlin

fun onClick(view: View) {
    view.requestPointerCapture()
}

Java

@Override
public void onClick(View view) {
    view.requestPointerCapture();
}

Once the request to capture the pointer is successful, Android calls onPointerCaptureChange(true). The system delivers the mouse events to the focused view in your app as long as it's in the same view hierarchy as the view that requested the capture. Other apps stop receiving mouse events until the capture is released, including ACTION_OUTSIDE events. Android delivers pointer events from sources other than the mouse normally, but the mouse pointer is not visible anymore.

Handle captured pointer events

Once a view has successfully acquired the pointer capture, Android starts delivering the mouse events. Your focused view can handle the events by performing one of the following tasks:

  1. If you're using a custom view, override onCapturedPointerEvent(MotionEvent).
  2. Otherwise, register an OnCapturedPointerListener.

The following code example shows how to implement onCapturedPointerEvent(MotionEvent):

Kotlin

override fun onCapturedPointerEvent(motionEvent: MotionEvent): Boolean {
    // Get the coordinates required by your app
    val verticalOffset: Float = motionEvent.y
    // Use the coordinates to update your view and return true if the event was
    // successfully processed
    return true
}

Java

@Override
public boolean onCapturedPointerEvent(MotionEvent motionEvent) {
  // Get the coordinates required by your app
  float verticalOffset = motionEvent.getY();
  // Use the coordinates to update your view and return true if the event was
  // successfully processed
  return true;
}

The following code example shows how to register an OnCapturedPointerListener:

Kotlin

myView.setOnCapturedPointerListener { view, motionEvent -&gt;
    // Get the coordinates required by your app
    val horizontalOffset: Float = motionEvent.x
    // Use the coordinates to update your view and return true if the event was
    // successfully processed
    true
}

Java

myView.setOnCapturedPointerListener(new View.OnCapturedPointerListener() {
  @Override
  public boolean onCapturedPointer (View view, MotionEvent motionEvent) {
    // Get the coordinates required by your app
    float horizontalOffset = motionEvent.getX();
    // Use the coordinates to update your view and return true if the event was
    // successfully processed
    return true;
  }
});

Whether you use a custom view or register a listener, your view receives a MotionEvent with pointer coordinates that specify relative movements such as X/Y deltas, similar to the coordinates delivered by a trackball device. You can retrieve the coordinates by using getX() and getY().

Release pointer capture

The view in your app can release the pointer capture by calling releasePointerCapture(), as shown in the following code example:

Kotlin

override fun onClick(view: View) {
    view.releasePointerCapture()
}

Java

@Override
public void onClick(View view) {
    view.releasePointerCapture();
}

The system can take the capture away from the view without you explicitly calling releasePointerCapture(), most commonly because the view hierarchy that contains the view that requested capture has lost focus.