In Practice

Let's write an example for this fine API. We want to keep track of ten fingers at most (there's no device yet that can track more, so we are on the safe side here). Android will assign pointer identifiers from 0 to 9 to these fingers in the sequence they touch the screen. So we keep track of each pointer identifier's coordinates and touch state

(touching or not), and output this information to the screen via a TextView. Let's call our test activity MultiTouchTest. Listing 4-4 shows the complete code.

Listing 4-4. MultiTouchTest.java; Testing the Multitouch API

package com.badlogic.androidgames;

import android.app.Activity;

import android.os.Bundle;

import android.view.MotionEvent;

import android.view.View;

import android.view.View.OnTouchListener;

import android.widget.TextView;

public class MultiTouchTest extends Activity implements OnTouchListener { StringBuilder builder = new StringBuilder(); TextView textView; float[] x = new float[10]; float[] y = new float[10]; boolean[] touched = new boolean[l0];

private void updateTextView() { builder.setLength(0); for(int i = 0; i < 10; i++) { builder.append(touched[i]); builder.append(", "); builder.append(x[i]); builder.append(", "); builder.append(y[i]); builder.append("\n");

textView.setText(builder.toString());

public void onCreate(Bundle savedlnstanceState) { super.onCreate(savedlnstanceState); textView = new TextView(this);

textView.setText("Touch and drag (multiple fingers supported)!");

textView.setOnTouchListener(this);

setContentView(textView);

^Override public boolean onTouch(View v, MotionEvent event) {

int action = event.getAction() & MotionEvent.ACTION_MASK;

int pointerlndex = (event.getAction() & MotionEvent.ACTION_POINTER_ID_MASK) >> MotionEvent. ACTION_POINTER_ID_SHIFT;

int pointerld = event.getPointerld(pointerlndex);

switch (action) { case MotionEvent.ACTION_DOWN: case MotionEvent. ACTION_POINTER_DOWN: touched[pointerId] = true; x[pointerId] = (int)event.getX(pointerlndex); y[pointerId] = (int)event.getY(pointerlndex); break;

case MotionEvent. ACTION_UP: case MotionEvent. ACTION_POINTER_UP: case MotionEvent. ACTION_CANCEL: touched[pointerId] = false; x[pointerId] = (int)event.getX(pointerIndex); y[pointerId] = (int)event.getY(pointerIndex); break;

case MotionEvent. ACTION_MOVE:

int pointerCount = event.getPointerCount(); for (int i = 0; i < pointerCount; i++) { pointerIndex = i;

pointerId = event.getPointerId(pointerIndex); x[pointerId] = (int)event.getx(pointerIndex); y[pointerId] = (int)event.getY(pointerIndex);

break;

updateTextView(); return true;

We implement the OnTouchListener interface as before. To keep track of the coordinates and touch state of the ten fingers, we add three new member arrays that will hold that information for us. The arrays x and y hold the coordinates for each pointer ID, and the array touched stores whether the finger with that pointer ID is down or not.

Next, I took the freedom to create a little helper method that will output the current state of the fingers to the TextView. It simply iterates through all the ten finger states and concatenates them via a StringBuilder. The final text is set to the TextView.

The onCreate() method sets up our activity and registers it as an OnTouchListener with the TextView. We already know that part by heart.

Now for the scary part: the onTouch() method. We start off by getting the event type by masking the integer returned by event.getAction(). Next we extract the pointer index and fetch the corresponding pointer identifier from the MotionEvent, as discussed earlier.

The heart of the onTouch() method is that big nasty switch statement, which we already used in a reduced form to process single-touch events. We group all the events into three categories on a high level:

A touch-down event happened (MotionEvent.ACTION_DOWN, MotionEvent.ACTION_PONTER_DOWN). We set the touch state for the pointer identifier to true, and also save the current coordinates of that pointer.

A touch-up event happened (MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP, MotionEvent.CANCEL). We set the touch state to false for that pointer identifier and save its last known coordinates.

One or more fingers were dragged across the screen (MotionEvent.ACTION_MOVE). We check how many events are contained in the MotionEvent and then update the coordinates for the pointer indices 0 to MotionEvent.getPointerCount() - 1. For each event, we fetch the corresponding pointer identifier and update the coordinates.

Once the event is processed, we update the TextView via a call to the updateView() method we defined earlier. Finally we return true, indicating that we processed the touch event.

Figure 4-7 shows the output of the activity after I touch two fingers on my Nexus One and drag them around a little.

IU0O ifflo® 10:47 pm

Multi-Touch Test false, 314.0, 226.0 false, 69.0, 623.0 false, 0.0,0.0 false, 0.0,0.0 false, 0.0, 0.0 false, 0.0,0.0 false, 0.0,0.0 false, 0.0,0.0 false, 0.0,0.0 false, 0.0,0.0

Figure 4-7. Fun with multitouch

There are a few things we can observe when we run this example:

If we start it on a device or emulator with an Android version lower than 2.0, we get a nasty exception, as we're use an API that is not available on those earlier versions. We can work around that by determining which Android version the application is running on, using the single-touch code on devices with Android 1.5 and 1.6, and using the multitouch code on devices with Android 2.0 or newer. We'll get back to that in the next chapter.

There's no multitouch on the emulator. The API is there if we create an emulator running Android version 2.0 or higher, but we only have a single mouse. And even if we had two mice, it wouldn't make a difference.

Touch two fingers down, lift the first one, and touch it down again. The second finger will keep its pointer identifier after the first finger is lifted. When the first finger is touched down for the second time, it gets the first free pointer identifier, which is 0 in this case. Any new finger that touches the screen will get the first free pointer identifier. That's a rule to remember.

If you try this on a Nexus One or a Droid, you will notice some strange behavior when your cross two fingers on one axis. This is due to the fact that the screens of those devices do not fully support the tracking of individual fingers. It's a big problem, but we can work around it somewhat by designing our UIs with some care. We'll have another look at the issue in a later chapter. The phrase to keep in mind is don't cross the streams!

And that's how multitouch processing works on Android. It is a pain in the buttocks, but once you untangle all the terminology and come to peace with the bit twiddling, it becomes somewhat OK to use.

NOTE: I'm sorry if this made your head explode. This section was rather heavy duty. Sadly, the official documentation for the API is extremely lacking, and most people "learn" the API by simply hacking away at it. I suggest you play around with the preceding code example until you fully grasp what's going on.

0 0

Post a comment