This lesson teaches you to
You should also read
- Input Events API Guide
- Sensors Overview
- Making the View Interactive
- Design Guide for Gestures
- Design Guide for Touch Feedback
Try it out
InteractiveChart.zip
A "touch gesture" occurs when a user places one or more fingers on the touch screen, and your application interprets that pattern of touches as a particular gesture. There are correspondingly two phases to gesture detection:
- Gathering data about touch events.
- Interpreting the data to see if it meets the criteria for any of the gestures your app supports.
Support Library Classes
The examples in this lesson use the
GestureDetectorCompat
and
MotionEventCompat
classes. These classes are in the
Support Library
. You should use
Support Library classes where possible to provide compatibility with devices
running Android 1.6 and higher. Note that
MotionEventCompat
is
not
a
replacement for the
MotionEvent
class. Rather, it provides static utility
methods to which you pass your
MotionEvent
object in order to receive
the desired action associated with that event.
Gather Data
When a user places one or more fingers on the screen, this triggers the
callback
onTouchEvent()
on the View that received the touch events.
For each sequence of touch events (position, pressure, size, addition of another finger, etc.)
that is ultimately identified as a gesture,
onTouchEvent()
is fired several times.
The gesture starts when the user first touches the screen, continues as the system tracks
the position of the user's finger(s), and ends by capturing the final event of
the user's fingers leaving the screen. Throughout this interaction,
the
MotionEvent
delivered to
onTouchEvent()
provides the details of every interaction. Your app can use the data provided by the
MotionEvent
to determine if a gesture it cares
about happened.
Capturing touch events for an Activity or View
To intercept touch events in an Activity or View, override
the
onTouchEvent()
callback.
The following snippet uses
getActionMasked()
to extract the action the user performed from the
event
parameter. This gives you the raw
data you need to determine if a gesture you care about occurred:
public class MainActivity extends Activity { ... // This example shows an Activity, but you would use the same approach if // you were subclassing a View. @Override public boolean onTouchEvent(MotionEvent event){ int action = MotionEventCompat.getActionMasked(event); switch(action) { case (MotionEvent.ACTION_DOWN) : Log.d(DEBUG_TAG,"Action was DOWN"); return true; case (MotionEvent.ACTION_MOVE) : Log.d(DEBUG_TAG,"Action was MOVE"); return true; case (MotionEvent.ACTION_UP) : Log.d(DEBUG_TAG,"Action was UP"); return true; case (MotionEvent.ACTION_CANCEL) : Log.d(DEBUG_TAG,"Action was CANCEL"); return true; case (MotionEvent.ACTION_OUTSIDE) : Log.d(DEBUG_TAG,"Movement occurred outside bounds " + "of current screen element"); return true; default : return super.onTouchEvent(event); } }
You can then do your own processing on these events to determine if a
gesture occurred. This is the kind of processing you would have to do for a
custom gesture. However, if your app uses
common gestures such as double tap, long press, fling, and so on, you can
take advantage of the
GestureDetector
class.
GestureDetector
makes it easy for you to detect common
gestures without processing the individual touch events yourself. This is
discussed below in
Detect Gestures
.
Capturing touch events for a single view
As an alternative to
onTouchEvent()
,
you can attach an
View.OnTouchListener
object to any
View
object using the
setOnTouchListener()
method. This makes it possible to to listen for touch
events without subclassing an existing
View
. For
example:
View myView = findViewById(R.id.my_view); myView.setOnTouchListener(new OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { // ... Respond to touch events return true; } });
Beware of creating a listener that returns
false
for the
ACTION_DOWN
event. If you do this, the listener will
not be called for the subsequent
ACTION_MOVE
and
ACTION_UP
string of events. This is because
ACTION_DOWN
is the starting point for all touch events.
If you are creating a custom View, you can override
onTouchEvent()
,
as described above.
Detect Gestures
Android provides the
GestureDetector
class for detecting
common gestures. Some of the gestures it supports include
onDown()
,
onLongPress()
,
onFling()
, and so
on. You can use
GestureDetector
in conjunction with the
onTouchEvent()
method described above.
Detecting All Supported Gestures
When you instantiate a
GestureDetectorCompat
object, one of the parameters it takes is a class that implements the
GestureDetector.OnGestureListener
interface.
GestureDetector.OnGestureListener
notifies users when
a particular touch event has occurred. To make it possible for your
GestureDetector
object to receive events, you override
the View or Activity's
onTouchEvent()
method,
and pass along all observed events to the detector instance.
In the following snippet, a return value of
true
from the individual
on
<TouchEvent>
methods indicates that you
have handled the touch event. A return value of
false
passes events down
through the view stack until the touch has been successfully handled.
Run the following snippet to get a feel for how actions are triggered when
you interact with the touch screen, and what the contents of the
MotionEvent
are for each touch event. You will realize how much
data is being generated for even simple interactions.
public class MainActivity extends Activity implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener{ private static final String DEBUG_TAG = "Gestures"; private GestureDetectorCompat mDetector; // Called when the activity is first created. @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Instantiate the gesture detector with the // application context and an implementation of // GestureDetector.OnGestureListener mDetector = new GestureDetectorCompat(this,this); // Set the gesture detector as the double tap // listener. mDetector.setOnDoubleTapListener(this); } @Override public boolean onTouchEvent(MotionEvent event){ this.mDetector.onTouchEvent(event); // Be sure to call the superclass implementation return super.onTouchEvent(event); } @Override public boolean onDown(MotionEvent event) { Log.d(DEBUG_TAG,"onDown: " + event.toString()); return true; } @Override public boolean onFling(MotionEvent event1, MotionEvent event2, float velocityX, float velocityY) { Log.d(DEBUG_TAG, "onFling: " + event1.toString()+event2.toString()); return true; } @Override public void onLongPress(MotionEvent event) { Log.d(DEBUG_TAG, "onLongPress: " + event.toString()); } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { Log.d(DEBUG_TAG, "onScroll: " + e1.toString()+e2.toString()); return true; } @Override public void onShowPress(MotionEvent event) { Log.d(DEBUG_TAG, "onShowPress: " + event.toString()); } @Override public boolean onSingleTapUp(MotionEvent event) { Log.d(DEBUG_TAG, "onSingleTapUp: " + event.toString()); return true; } @Override public boolean onDoubleTap(MotionEvent event) { Log.d(DEBUG_TAG, "onDoubleTap: " + event.toString()); return true; } @Override public boolean onDoubleTapEvent(MotionEvent event) { Log.d(DEBUG_TAG, "onDoubleTapEvent: " + event.toString()); return true; } @Override public boolean onSingleTapConfirmed(MotionEvent event) { Log.d(DEBUG_TAG, "onSingleTapConfirmed: " + event.toString()); return true; } }
Detecting a Subset of Supported Gestures
If you only want to process a few gestures, you can extend
GestureDetector.SimpleOnGestureListener
instead of implementing
the
GestureDetector.OnGestureListener
interface.
GestureDetector.SimpleOnGestureListener
provides an implementation
for all of the
on
<TouchEvent>
methods by returning
false
for all of them. Thus you can override only the methods you care about.
For
example, the snippet below creates a class that extends
GestureDetector.SimpleOnGestureListener
and overrides
onFling()
and
onDown()
.
Whether or not you use
GestureDetector.OnGestureListener
,
it's best practice to implement an
onDown()
method that returns
true
. This is because all gestures begin with an
onDown()
message. If you return
false
from
onDown()
,
as
GestureDetector.SimpleOnGestureListener
does by default,
the system assumes that you want to ignore the rest of the gesture, and the other methods of
GestureDetector.OnGestureListener
never get called.
This has the potential to cause unexpected problems in your app.
The only time you should return
false
from
onDown()
is if you truly want to ignore an entire gesture.
public class MainActivity extends Activity { private GestureDetectorCompat mDetector; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mDetector = new GestureDetectorCompat(this, new MyGestureListener()); } @Override public boolean onTouchEvent(MotionEvent event){ this.mDetector.onTouchEvent(event); return super.onTouchEvent(event); } class MyGestureListener extends GestureDetector.SimpleOnGestureListener { private static final String DEBUG_TAG = "Gestures"; @Override public boolean onDown(MotionEvent event) { Log.d(DEBUG_TAG,"onDown: " + event.toString()); return true; } @Override public boolean onFling(MotionEvent event1, MotionEvent event2, float velocityX, float velocityY) { Log.d(DEBUG_TAG, "onFling: " + event1.toString()+event2.toString()); return true; } } }