Single and multi touch in Android
This tutorial describes how to use the touch API in Android applications.
Table of Contents
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.The base class for touch support is the
MotionEvent
class which is passed to Views
via theonTouchEvent()
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
.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) |
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
andMotionEvent.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 ofMotionEvent
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;
}
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.The following assumes that you have already basic knowledge in Android development.
We will demonstrate Singletouch with an custom
View
.Create an Android project called com.vogella.android.touch.single with the activity calledSingleTouchActivity.
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));
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_single_touch, menu);
return true;
}
}
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).
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 aPath
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.
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.
Create the Android project called de.vogella.android.touch.scaledetector with an Activity calledScaleDetectorTestActivity.
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).
The blog on handling single and multi-touch on Android is a well-crafted tutorial that demystifies touch input for developers. https://www.mobilezmarket.com/
ReplyDelete