Linux.com

Home Learn Linux Linux Tutorials Android Application Development Tutorial: How to Handle Multi-Touch

Android Application Development Tutorial: How to Handle Multi-Touch

In the last Android tutorial, we looked at handling a touchscreen event. This only handled a single pointer, though. Android can handle multiple pointers at the same time, reflecting what happens when you have more than one finger on the screen at the same time. This is how multi-touch gestures (like pinch-zoom) work.

android multi-touchIn fact, we can see Android supporting multiple pointers by demonstrating a bug in the last tutorial's code. Run the BubbleMove app from the last tutorial, and start dragging the bubble around the screen. If you put a second finger on the first screen while you're doing this, then lift the first finger, the bubble will jump across the screen to the second finger location. This is because the code as it currently stands handles only the default pointer. The first finger down is the default pointer; but when that is taken off the screen, the second finger (as the only remaining pointer) becomes default, and the bubble jumps across to this new default pointer. Let's look at how to handle multiple pointers explicitly.

One quick note: unfortunately the emulator only has experimental support for multi-touch. To follow along with this tutorial, you'll need to hook up your hardware device for testing, or experiment with the tethered device option at the previous link.

Handling Multiple Pointers: The Basics

We'll start out our journey into multi-touch handling by fixing that multiple-pointer bug. To do this, we need to take pointer ID into account in our code.

The only part of the code for the last tutorial that we need to change is onTouchEvent(). Edit it to look like this (with an additional couple of private class variables):

private static final int INVALID_POINTER_ID = -1;
private int activePointer = INVALID_POINTER_ID;
public boolean onTouchEvent(MotionEvent e) {
  switch (e.getActionMasked()) { 
    case MotionEvent.ACTION_DOWN:
      thread.setBubble(e.getX(), e.getY());
      activePointer = e.getPointerId(0);
      break;
    case MotionEvent.ACTION_MOVE:
      if (activePointer != INVALID_POINTER_ID) {
        int pointerIndex = e.findPointerIndex(activePointer);
        thread.setBubble(e.getX(pointerIndex), e.getY(pointerIndex));
      }
      break;
    case MotionEvent.ACTION_UP:
      if (activePointer != INVALID_POINTER_ID) {
        showTotalTime(e.getEventTime() - e.getDownTime());
        activePointer = INVALID_POINTER_ID;
      }
      break;
    case MotionEvent.ACTION_CANCEL: 
      activePointer = INVALID_POINTER_ID;
      break;
    case MotionEvent.ACTION_POINTER_UP:
      int pointerIndex = e.getActionIndex();
      int pointerId = e.getPointerId(pointerIndex);
      if (pointerId == activePointer) {
        showTotalTime(e.getEventTime() - e.getDownTime());
        activePointer = INVALID_POINTER_ID;
      }
      break;
    }
  return true;
}

We're now using getActionMasked() instead of getAction() for the switch statement. getAction() returns only an action if there's a single pointer, but with multiple pointers, it returns a combination of the pointer action and a shifted pointer index. To avoid us shifting out the pointer index ourselves, we can use getActionMasked(), which simply returns an action (ACTION_UPACTION_POINTER_DOWN, etc), and if this is a pointer action, use getActionIndex() to get the pointer index.

Take a look at ACTION_POINTER_UP, which is the crucial part of the new code. An ACTION_POINTER_UP event means that a pointer has gone up, but not the only pointer (if there is only one pointer, and that goes up, an ACTION_UP event is sent), and not necessarily the active pointer. So we get the index of this pointer, and the pointer ID associated with it. If this is the active pointer -- the first finger we put on the screen -- then we're done with moving our bubble. In other words, in this version of the code, we only pay attention to that first pointer, and explicitly ignore any further pointers. Nothing else will happen to the bubble until all fingers come off the screen and a new set of touch events happens.

If the active pointer has gone up, then, we show the total time of the drag gesture, and set the active pointer variable to INVALID_POINTER_ID, to show that it is no longer active.

Now take a look at ACTION_DOWN. If this action is sent, then this is the first pointer to go down; so we treat it as the active pointer, and use getPointerId to save the pointer ID as the active pointer.

With both ACTION_UP and ACTION_MOVE, before doing anything, we check that there is a valid active pointer. ACTION_MOVE is sent from any active pointer on the screen, so we only want to handle that data if it comes from our own active pointer.

ACTION_UP is only sent when the final pointer goes up; but that final pointer could be our second pointer, in which case we ignore it. To make that a bit clearer, here's a possible sequence of pointer actions:

  1. Pointer 1 (active pointer) goes down: ACTION_DOWN.
  2. Pointer 2 (non-active pointer) goes down: ACTION_POINTER_DOWN.
  3. Both pointers move: ACTION_MOVE.
  4. Pointer 1 (active pointer) goes up: ACTION_POINTER_UP.
  5. Pointer 2 (non-active pointer) continues to move: ACTION_MOVE.
  6. Pointer 2 (non-active pointer) goes up: ACTION_UP.

In that list, we only want to respond to the Pointer 1 events. So if we get an ACTION_UP event, we check first whether we still have a valid active pointer. If not, then that ACTION_UP event is coming from Pointer 2, and we ignore it. If the active pointer is still valid, we show the total time Toast message.

More pointer handling

What if you want to handle the event of a second pointer going down? This would set a pointer index and pop a Toast message up:

private int newPointer = INVALID_POINTER_ID;
public boolean onTouchEvent(MotionEvent e) {
  // ... code as before ...
  case MotionEvent.ACTION_POINTER_DOWN:
        	  int newPointerIndex = e.getActionIndex();
        	  newPointer = e.getPointerId(newPointerIndex);
        	  Toast.makeText(ctx, "New pointer!", Toast.LENGTH_SHORT).show();
        	  break;
  // ... rest of code...
}

Pretty straightforward. Here's an interesting wrinkle, though. If you put in code to handle your second pointer just the same way as your first, you'll fetch up with something like this:

public boolean onTouchEvent(MotionEvent e) {
  switch (e.getActionMasked()) {
    // ACTION_DOWN, ACTION_CANCEL, ACTION_POINTER_DOWN as above
    case MotionEvent.ACTION_MOVE:
   	  if (newPointer != INVALID_POINTER_ID) {
        int pointerIndex = e.findPointerIndex(newPointer);
        thread.setBubble(e.getX(pointerIndex), e.getY(pointerIndex));
      }
      if (activePointer != INVALID_POINTER_ID) {
	    int pointerIndex = e.findPointerIndex(activePointer);
	    thread.setBubble(e.getX(pointerIndex), e.getY(pointerIndex));
      }
      break;
    case MotionEvent.ACTION_UP:
      if (activePointer != INVALID_POINTER_ID) {
        showTotalTime(e.getEventTime() - e.getDownTime());
	    activePointer = INVALID_POINTER_ID;
      }
      if (newPointer != INVALID_POINTER_ID) {
        newPointer = INVALID_POINTER_ID;
      }
      break;
    case MotionEvent.ACTION_POINTER_UP:
      // get pointerIndex and pointerId as before
      if (pointerId == activePointer) {
   	    showTotalTime(e.getEventTime() - e.getDownTime());
   	    activePointer = INVALID_POINTER_ID;
      }
      if (pointerId == newPointer) {
   	    newPointer = INVALID_POINTER_ID;
      }
      break;
    }
  return true;
}

Run this and you'll see that as you lift a finger or put it down again, the bubble hops between fingers and keeps moving smoothly. However, if you put two fingers down and move both of them, the bubble moves with the first finger. Now, try switching the order of the ACTION_MOVE case, like this:

case MotionEvent.ACTION_MOVE:
  if (activePointer != INVALID_POINTER_ID) {
    int pointerIndex = e.findPointerIndex(activePointer);
    thread.setBubble(e.getX(pointerIndex), e.getY(pointerIndex));
  }
  if (newPointer != INVALID_POINTER_ID) {
    int pointerIndex = e.findPointerIndex(newPointer);
    thread.setBubble(e.getX(pointerIndex), e.getY(pointerIndex));
  }
  break;

Compile and run, and you'll find that this time, the bubble follows the second pointer around. What's happening is that it's nearly impossible to hold a finger still on the screen, so ACTION_MOVE events are being sent by both pointers all the time. They're too fast for the human eye to follow, so the one that you notice is whichever is evaluated second. If you want something more complicated to happen -- for example, for the bubble to bounce visibly between the two pointers, or to follow an average of both of them -- you'll need to do more complicated processing to track and average both pointers.

GestureDetectors

If you want to do complicated things with multiple fingers, you'll probably want to handle your pointers directly, as in this tutorial. But a lot of the time, you might want to handle one of a set of standard gestures, such as pinch zoom. In this case, a GestureDetector -- a filter object that turns MotionEvents into gesture events -- is likely to be helpful. In the next tutorial, we'll look at using GestureDetector and OnGestureListener to handle multi-touch events.

 

Comments

Subscribe to Comments Feed
  • Stevie Whalen Said:

    Thnaks for sharing great post. Well an Android is the first free, open source and fully customizable mobile platform. There is a huge demand for android application development. Now I can easily work on multiple pointer using this information. Look forward to see your next post.

  • Steve Dahison Said:

    Thanks for very nice post. Today Android is most popular open source and customizable mobile platform. It can easily work on multiple pointer using this information.

  • Shruti Garg Said:

    Thanks for such a nice and informative post. please help me regarding the entries in the manifest file. I am thinking of learning Android online development course as I want to learn android at a good level. I have search well for online android course then I get a course which includes java as well. Please help me resolving my problem regarding online android course as I don't know much about the course and are they offering a good informative material at a good price? http://www.wiziq.com/course/21661-learn-java-android-professional-android-with-project-june-batch as this is the course I am talking about. Please help me. Thanks

  • Gergana_StarApplication Said:

    You are young, smart and you have a great idea for an Android app that you would like to see a reality. We can help you monetize all your ideas with the help of our amazing advertisers and solutions. Learn how to develop your ideas and manage to make a living out of it. Get paid for every download of your idea. Our advertisers can promote you onto a market of customers just looking for the “next big thing”. Discover the new StarApplication Solution.

  • Darren Said:

    Hello! Is somebody here using the StarApplication SDK? I just wanted to ask about your opinion:)

  • android application devlopment Said:

    thanks for sharing a great post , Android is a quickly growing mobile OS that is sophisticated but easy to use.mobile applications that run on the latest and most popular Android platforms using the standard Android Software Development Kit to build superior quality Android apps. Mobile applications can allow a business to provide better customer support, showcase products, give customers more opportunities to purchase, and raise awareness of a brand.

  • Rishabh Said:

    Hi Juliet, I am Rishabh and I'm on the marketing team WizIQ.com (3m+ students and 200,000+ teachers and instructors in a LinkedIn meets Salesforce model for education, with one of the best virtual classrooms in the business). We’ve got a thriving learning marketplace with some of the best names in business. The demand for high quality self-paced and LIVE courses is growing and having seen your skills, we'd like to welcome you with a free premium membership of WizIQ. Creating a course is optional (but we'd love it and you keep 90% of the revenue). Quality courses across categories see rapid adoption on WizIQ (see this course http://www.wiziq.com/course/21661-learn-java-android-professional-android-with-project-june-batch ) for instance or many others. If you'd like to get started with your complimentary premium membership account (unlimited LIVE online classes, recordings, courses, discussions etc.), just buzz me at rishabh@wiziq.com Best regards, Rishabh

  • Facebookapplicationdevelopment Said:

    Thank for submitted in your post

  • android application development in chennai Said:

    nice information.....thank you......

  • dimamohmeh Said:

    Visit Us at http://www.tutorial-how.com or at http://un1lock.blogspot.com


Who we are ?

The Linux Foundation is a non-profit consortium dedicated to the growth of Linux.

More About the foundation...

Frequent Questions

Join / Linux Training / Board