Support free tutorials:











vogella training Training Books



Handling single and multi touch on Android - Tutorial

Lars Vogel

Version 1.4

20.08.2013

Revision History
Revision 0.1 03.06.2011 Lars
Vogel
Created
Revision 0.2 - 1.4 15.06.2011 - 20.08.2013 Lars
Vogel
bug fixes and enhancements

Single and multi touch in Android

This tutorial describes how to use the touch API in Android applications.


Table of Contents

1. Android Touch
1.1. Android touch basics
1.2. Single touch
1.3. Multi touch
1.4. GestureDetectors
2. Prerequisites
3. Exercise: Singletouch Example
3.1. Draw via touch
3.2. Tracking
4. Exercise: Multitouch
5. Tutorial: ScaleGestureDetector
6. Support free vogella tutorials
6.1. Thank you
6.2. Questions and Discussion
7. Links and Literature
7.1. Source Code
7.2. Android Touch resources
7.3. Android Resources
7.4. vogella Resources

1. Android Touch

1.1. Android touch basics

The Android standard View class support touch events. You can react to touch events in your custom views and your activities. Android supports multiple pointers, e.g. fingers which are interacting with the screen.

Android touch

The base class for touch support is the MotionEvent class which is passed to Views via the onTouchEvent() method. To react to touch events you override the onTouchEvent() method.

The MotionEvent class contains the touch related information, e.g., the number of pointers, the X/Y coordinates and size and pressure of each pointer.

This method returns true if the touch event has been handled by the view. Android tries to find the deepest view which returns true to handles the touch event. If the view is part of another view (parent view), the parent can claim the event by returning true from the onInterceptTouchEvent() method. This would send an MotionEvent.ACTION_CANCEL event to the view which received previously the touch events.

To react to touch events in an activity, register an OnTouchListener for the relevant Views.

1.2. Single touch

If single input is used you can use the getX() and getY() methods to get the current position of the first finger.

Via the getAction() method you receive the action which was performed. The MotionEvent class provides the following constants to determine the action which was performed.

Table 1. Touch Events

Event Description
MotionEvent.ACTION_DOWN New touch started
MotionEvent.ACTION_MOVE Finger is moving
MotionEvent.ACTION_UP Finger went up
MotionEvent.ACTION_CANCEL Current event has been canceled, something else took control of the touch event
MotionEvent.ACTION_POINTER_DOWN Pointer down (multi-touch)
MotionEvent.ACTION_POINTER_UP Pointer up (multi-touch)


1.3. Multi touch

Multi-touch is available since Android 2.0 and has been improved in the version 2.2. This description uses the API as of version 2.2.

The MotionEvent.ACTION_POINTER_DOWN and MotionEvent.ACTION_POINTER_UP are send starting with the second finger. For the first finger MotionEvent.ACTION_DOWN and MotionEvent.ACTION_UP are used.

The getPointerCount() method on MotionEvent allows you to determine the number of pointers on the device. All events and the position of the pointers are included in the instance of MotionEvent which you receive in the onTouch() method.

To track the touch events from multiple pointers you have to use the MotionEvent.getActionIndex() and the MotionEvent.getActionMasked() methods to identify the index of the pointer and the touch event which happened for this pointer.

This pointer index can change over time, e.g. if one finger is lifted from the device. The stable version of a pointer is the pointer id, which can be determined with the getPointerId(pointerIndex) method from the MotionEvent object.

The usage if demonstrated in the following code snippet.

@Override
public boolean onTouchEvent(MotionEvent event) {

    // get pointer index from the event object
    int pointerIndex = event.getActionIndex();

    // get pointer ID
    int pointerId = event.getPointerId(pointerIndex);

    // get masked (not specific to a pointer) action
    int maskedAction = event.getActionMasked();

    switch (maskedAction) {

    case MotionEvent.ACTION_DOWN:
    case MotionEvent.ACTION_POINTER_DOWN: {
      // TODO use data
      break;
    }
    case MotionEvent.ACTION_MOVE: { // a pointer was moved
      // TODO use data
      break;
    }
    case MotionEvent.ACTION_UP:
    case MotionEvent.ACTION_POINTER_UP:
    case MotionEvent.ACTION_CANCEL: {
      // TODO use data
      break;
    }
    }
    invalidate();

    return true;
} 

Tip

Multitouch can not be tested on the emulator. You need a real Android device as input device.

1.4. GestureDetectors

Android provide the GestureDetector class which allow to consume MotionEvents and to create higher level gesture events to listeners.

For example the ScaleGestureDetector class allows to determine the predefined gesture of increasing and decreasing the size of the object via two fingers.

2. Prerequisites

The following assumes that you have already basic knowledge in Android development.

3. Exercise: Singletouch Example

3.1. Draw via touch

We will demonstrate Singletouch with an custom View.

Create an Android project called com.vogella.android.touch.single with the activity called SingleTouchActivity.

Create the following SingleTouchEventView class which implements a View which supports single touch.

package de.vogella.android.touch.single;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class SingleTouchEventView extends View {
  private Paint paint = new Paint();
  private Path path = new Path();

  public SingleTouchEventView(Context context, AttributeSet attrs) {
    super(context, attrs);

    paint.setAntiAlias(true);
    paint.setStrokeWidth(6f);
    paint.setColor(Color.BLACK);
    paint.setStyle(Paint.Style.STROKE);
    paint.setStrokeJoin(Paint.Join.ROUND);
  }

  @Override
  protected void onDraw(Canvas canvas) {
    canvas.drawPath(path, paint);
  }

  @Override
  public boolean onTouchEvent(MotionEvent event) {
    float eventX = event.getX();
    float eventY = event.getY();

    switch (event.getAction()) {
    case MotionEvent.ACTION_DOWN:
      path.moveTo(eventX, eventY);
      return true;
    case MotionEvent.ACTION_MOVE:
      path.lineTo(eventX, eventY);
      break;
    case MotionEvent.ACTION_UP:
      // nothing to do
      break;
    default:
      return false;
    }

    // Schedules a repaint.
    invalidate();
    return true;
  }
} 

Add this view to your activity.

package de.vogella.android.touch.single;

import android.app.Activity;
import android.os.Bundle;

public class SingleTouchActivity extends Activity {
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(new SingleTouchEventView(this, null));
  }
} 

If you run your application you will be able to draw on the screen with your finger (or with the mouse in the emulator).

Change your coding so that you use a layout definition based on XML. Hint: to use your own view in an XML layout definition you have to use the full-qualified class name (class including package information).

3.2. Tracking

Add code to your drawing example so that the current position of a finger is marked via a circle. To draw a circle you can use the addCircle(x, y, 50, Path.Direction.CW) method call on a Path or use the canvas element directly.

Make sure that only the current position is highlighted with a circle. The circle should appears as soon as the finger goes down and vanish once the finger goes up.

The result should look like the following.

Activity showing the drawing View

4. Exercise: Multitouch

In this exercise you create a view which support multitouch and allows you to track several fingers on your device. On the Android emulator you can only simulate singletouch with the mouse.

Create an Android project called com.vogella.android.multitouch with the >activity called MainActivity.

Create the following MultitouchView class.

package com.vogella.android.multitouch;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.MotionEvent;
import android.view.View;

public class MultitouchView extends View {

  private static final int SIZE = 60;

  private SparseArray<PointF> mActivePointers;
  private Paint mPaint;
  private int[] colors = { Color.BLUE, Color.GREEN, Color.MAGENTA,
      Color.BLACK, Color.CYAN, Color.GRAY, Color.RED, Color.DKGRAY,
      Color.LTGRAY, Color.YELLOW };

  private Paint textPaint;


  public MultitouchView(Context context, AttributeSet attrs) {
    super(context, attrs);
    initView();
  }

  private void initView() {
    mActivePointers = new SparseArray<PointF>();
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    // set painter color to a color you like
    mPaint.setColor(Color.BLUE);
    mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
    textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    textPaint.setTextSize(20);
  }

  @Override
  public boolean onTouchEvent(MotionEvent event) {

    // get pointer index from the event object
    int pointerIndex = event.getActionIndex();

    // get pointer ID
    int pointerId = event.getPointerId(pointerIndex);

    // get masked (not specific to a pointer) action
    int maskedAction = event.getActionMasked();

    switch (maskedAction) {

    case MotionEvent.ACTION_DOWN:
    case MotionEvent.ACTION_POINTER_DOWN: {
      // We have a new pointer. Lets add it to the list of pointers

      PointF f = new PointF();
      f.x = event.getX(pointerIndex);
      f.y = event.getY(pointerIndex);
      mActivePointers.put(pointerId, f);
      break;
    }
    case MotionEvent.ACTION_MOVE: { // a pointer was moved
      for (int size = event.getPointerCount(), i = 0; i < size; i++) {
        PointF point = mActivePointers.get(event.getPointerId(i));
        if (point != null) {
          point.x = event.getX(i);
          point.y = event.getY(i);
        }
      }
      break;
    }
    case MotionEvent.ACTION_UP:
    case MotionEvent.ACTION_POINTER_UP:
    case MotionEvent.ACTION_CANCEL: {
      mActivePointers.remove(pointerId);
      break;
    }
    }
    invalidate();

    return true;
  }

  @Override
  protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    // draw all pointers
    for (int size = mActivePointers.size(), i = 0; i < size; i++) {
      PointF point = mActivePointers.valueAt(i);
      if (point != null)
        mPaint.setColor(colors[i % 9]);
      canvas.drawCircle(point.x, point.y, SIZE, mPaint);
    }
    canvas.drawText("Total pointers: " + mActivePointers.size(), 10, 40 , textPaint);
  }

} 

Add this view to the layout of your activity.

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.vogella.android.multitouch.MultitouchView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />

</RelativeLayout> 

Your generated activity can remain the same.

package com.vogella.android.multitouch;

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;

public class MainActivity extends Activity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
  }

  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.activity_main, menu);
    return true;
  }

} 

If you run your application you will be able to draw on the screen with your fingers. Every device has an upper limit how many pointers are supported, test out how many simultaneous pointers your device supports. This application should look similar to the following screenshot.

Multitouch exercise

5. Tutorial: ScaleGestureDetector

Create the Android project called de.vogella.android.touch.scaledetector with an Activity called ScaleDetectorTestActivity.

Create the following class.

package de.vogella.android.touch.scaledetector;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;

public class ImageViewWithZoom extends View {
  private Drawable image;
  private float scaleFactor = 1.0f;
  private ScaleGestureDetector scaleGestureDetector;

  public ImageViewWithZoom(Context context) {
    super(context);
    image = context.getResources().getDrawable(R.drawable.icon);
    setFocusable(true);
    image.setBounds(0, 0, image.getIntrinsicWidth(),
        image.getIntrinsicHeight());
    scaleGestureDetector = new ScaleGestureDetector(context,
        new ScaleListener());
  }

  @Override
  protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    // Set the image bounderies
    canvas.save();
    canvas.scale(scaleFactor, scaleFactor);
    image.draw(canvas);
    canvas.restore();
  }

  @Override
  public boolean onTouchEvent(MotionEvent event) {
    scaleGestureDetector.onTouchEvent(event);
    invalidate();
    return true;
  }

  private class ScaleListener extends
      ScaleGestureDetector.SimpleOnScaleGestureListener {
    @Override
    public boolean onScale(ScaleGestureDetector detector) {
      scaleFactor *= detector.getScaleFactor();

      // don't let the object get too small or too large.
      scaleFactor = Math.max(0.1f, Math.min(scaleFactor, 5.0f));

      invalidate();
      return true;
    }
  }
} 

Add this View to your activity.

package de.vogella.android.touch.scaledetector;

import android.app.Activity;
import android.os.Bundle;

public class ScaleDetectorTestActivity extends Activity {
  
/** Called when the activity is first created. */
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(new ImageViewWithZoom(this)); } }

If you run your application you should be able to shrink and enlarge the image via a multi-touch gesture (pitch zoom).

6. Support free vogella tutorials

Maintaining high quality free online tutorials is a lot of work. Please support free tutorials by donating or by reporting typos and factual errors.

6.1. Thank you

Please consider a contribution if this article helped you.

Flattr this

6.2. Questions and Discussion

If you find errors in this tutorial, please notify me (see the top of the page). Please note that due to the high volume of feedback I receive, I cannot answer questions to your implementation. Ensure you have read the vogella FAQ as I don't respond to questions already answered there.

7. Links and Literature

7.1. Source Code

Source Code of Examples

7.2. Android Touch resources

Google blog post about touch and multi touch

7.4. vogella Resources

vogella Training Android and Eclipse Training from the vogella team

Android Tutorial Introduction to Android Programming

GWT Tutorial Program in Java, compile to JavaScript and HTML

Eclipse RCP Tutorial Create native applications in Java

JUnit Tutorial Test your application

Git Tutorial Put all your files in a distributed version control system