Processing Single Touch Events

When we processed clicks on a button in Chapter 2, we saw that listener interfaces are the way Android reports events to us. Touch events are no different. Touch events are passed to an OnTouchListener interface implementation that we register with a View. The OnTouchListener interface has only a single method:

public abstract boolean onTouch (View v, MotionEvent event)

The first argument is the View that the touch events get dispatched to. The second argument is what we'll dissect to get the touch event.

An OnTouchListener can be registered with any View implementation via the View.setOnTouchListener() method. The OnTouchListener will be called before the MotionEvent is dispatched to the View itself. We can signal to the View in our implementation of the onTouch() method that we have already processed the event by returning true from the method. If we return false, the View itself will process the event.

The MotionEvent instance has three methods that are relevant to us:

MotionEvent.getX() and MotionEvent.getY(): These methods report the x- and y-coordinate of the touch event relative to the View. The coordinate system is defined with the origin in the top left of the view, the x-axis points to the right, and the y-axis points downward. The coordinates are given in pixels. Note that the methods return floats, and thus the coordinates have subpixel accuracy.

MotionEvent.getAction(): This returns the type of the touch event. It is an integer that takes on one of the values MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE, MotionEvent.ACTION_CANCEL, and MotionEvent.ACTION_UP.

Sounds simple, and it really is. The MotionEvent.ACTION_DOWN event happens when the finger touches the screen. When the finger moves, events with type MotionEvent.ACTION_MOVE are fired. Note that you will always get MotionEvent.ACTION_MOVE events, as you can't hold your finger still enough to avoid them. The touch sensor will recognize the slightest change. When the finger is lifted up again, the MotionEvent.ACTION_UP event is reported. MotionEvent.ACTION_CANCEL events are a bit of a mystery. The documentation says they will be fired when the current gesture is canceled. I have never come across that event in real life yet. However, we'll still process it and pretend it is a MotionEvent.ACTION_UP event when we start implementing our first game.

Let's write a simple test activity to see how this works in code. The activity should display the current position of the finger on the screen, as well as the event type. Listing 4-3 shows you what I came up with.

Listing 4-3. SingleTouchTest.java; Testing Single-Touch Handling package com.badlogic.androidgames;

import android.app.Activity;

import android.os.Bundle;

import android.util.Log;

import android.view.MotionEvent;

import android.view.View;

import android.view.View.OnTouchListener;

import android.widget.TextView;

public class SingleTouchTest extends Activity implements OnTouchListener { StringBuilder builder = new StringBuilder(); TextView textView;

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState); textView = new TextView(this);

textView.setText("Touch and drag (one finger only)!"); textView.setOnTouchListener(this); setContentView(textView);

^Override public boolean onTouch(View v, MotionEvent event) { builder.setLength(o); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: builder.append("down, "); break;

case MotionEvent. ACTION_MOVE: builder.append("move, "); break;

case MotionEvent .ACTION_CANCEL: builder.append("cancle, "); break;

case MotionEvent.ACTION_UP: builder.append("up, "); break;

builder.append(event.getX()); builder.append(", "); builder.append(event.getY()); String text = builder.toString(); Log.d("TouchTest", text); textView.setText(text); return true;

We let our activity implement the OnTouchListener interface. We also have two members, one for the TextView, and a StringBuilder we'll use to construct our event strings.

The onCreate() method is pretty self-explanatory. The only novelty is the call to TextView.setOnTouchListener(), where we register our activity with the TextView so it receives MotionEvents.

What's left is the onTouch() method implementation itself. We ignore the view argument, as we know that it must be the TextView. All we are interested in is getting the touch event type, appending a string identifying it to our StringBuilder, appending the touch coordinates, and updating the TextView text. That's it. We also log the event to LogCat so we can see the order in which the events happen, as the TextView will only show the last event that we processed (we clear the StringBuilder every time onTouch() is called).

One subtle detail in the onTouch() method is the return statement, where we return true. Usually we'd stick to the listener concept and return false to not interfere with the event-dispatching process. If we do this in our example, we won't get any events other than the MotionEvent.ACTION_DOWN event. So we tell the TextView that we just consumed the event. That behavior might differ between different View implementations. Luckily we'll only need three other views in the rest of this book, and those will happily let us consume any event we want.

If we fire that application up on the emulator or a connected device, we can see how the TextView will always display the last event type and position reported to the onTouch() method. Additionally, you can see the same messages in LogCat.

I did not fix the orientation of the activity in the manifest file. If you rotate your device so that the activity is in landscape mode, the coordinate system of course changes. Figure 4-6 shows you the activity in portrait and landscape mode. In both cases I tried to touch the middle of the View. Note how the x- and y-coordinates seem to get swapped. The figure also shows you the x- and y-axes in both cases (the yellow lines) along with the point on the screen that I roughly touched (the green circle). In both cases the origin is in the upper-left corner of the TextView, with the x-axis pointing to the right and the y-axis pointing downward.

Touch Test

jp, 244.797,413.53525

X

O

/y

Figure 4-6. Touching the screen in portrait and landscape modes

Touch Test

Lip, 397.23654,198.6872

/

X

o

,y

Figure 4-6. Touching the screen in portrait and landscape modes

Depending on the orientation, our maximum x and y values change, of course. The preceding images were taken on a Nexus One, which has a screen resolution of 480x800 pixels in portrait mode (800x480 in landscape mode). Since the touch coordinates are given relative to the View, and since the view doesn't fill the complete screen, our maximum y value will be smaller than the resolution height. We'll later see how we can enable full-screen mode so the title bar and notification bar don't get in our way.

Sadly there are a few issues with touch events on older Android versions and firstgeneration devices:

Touch event flood: The driver will report as many touch events as possible when a finger is down on the touchscreen—on some devices hundreds per second. We can fix this issue by putting a Thread.sleep(l6) call into our onTouch() method, which will put the UI thread on which those events are dispatched to sleep for 16 milliseconds. With this we'll get 60 events per second at most, which is more than enough to have a responsive game. This is only a problem on devices with Android version 1.5.

Touching the screen eats the CPU: Even if we sleep in our onTouch() method, the system has to process the events in the kernel as reported by the driver. On old devices such as the Hero or G1, this can use up to 50 percent of the CPU, which leaves a lot less processing power for our main loop thread. As a consequence, our perfectly fine frame rate will drop considerably, sometimes to the point where the game becomes unplayable. On second-generation devices, the problem is a lot less pronounced and can usually be neglected. Sadly, there's no solution for this on older devices.

In general you will want to put Thread.sleep(l6) in all your onTouch() methods just to make sure. On newer devices it will have no effect; on older devices it at least prevents the touch event flooding.

With the first generation of devices slowly dying out, this becomes less of a problem the more time passes. It still causes major grief among game developers, though. Try to explain to your users that your game runs like molasses cause something in the driver is using up all the CPU. Yeah, nobody will care.

0 0

Responses

  • pansy lothran
    How to get the view when x and y coordinates are known in android?
    7 years ago

Post a comment