Home Blog Page 1970

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.

Rush to Enable Enterprise Mobile Development Pits Native Against Container Approaches

Embarcadero RAD Studio XE4 allows developers to gain more control over the development lifecycle and deliver apps with tighter security, a better user experience, lightning quick performance, and a small footprint.

Rugged Micro-Box Computer Gets 1.86GHz Dual-Core Atom

Acnodes introduced a compact, rugged, fanless “micro-box” computer that can run embedded Linux on its dual-core 1.86GHz Intel Atom D2550 processor. The FES2215 supports up to 4GB DDR3 of RAM, accommodates an internal 2.5-inch SATA drive, drives simultaneous VGA and HDMI displays, and operates over a -20 to 60° C temperature range. Designed primarily for […]

Read more at LinuxGizmos

ARM Mini-ITX SBC Gets Serious About Serial

Cadia Networks announced a Mini-ITX motherboard that runs embedded Linux or Android on a 1.2GHz ARM Cortex-A8 Freescale i.MX53 SOC (system-on-chip) and features 12 onboard serial ports. The MIT-5500 is equipped with 1GB of DDR3 RAM and up to 4GB NAND flash, and offers dual display HD video on VGA, HDMI, or LVDS displays. The […]

Read more at LinuxGizmos

Amazon: “Infrastructure Is Not A Differentiator (Except For Us)”

The infrastructure rich keep getting richer, and that’s just fine, according to Amazon CTO Werner Vogel. As he declared at AWS Summit NYC last week, “infrastructure… is not a differentiator” for the enterprises and startups that keep buying servers to fill their own data centers. Given that Amazon has better scale and operational efficiency, Vogel’s argument goes, there’s no reason to continue trying to reinvent a wheel better built and operated by Amazon.

Self-serving? Sure. But true? Very likely, yes.

 

Read more at ReadWriteCloud

Windows 8.1 Set to Bring Back the Start Button

Win81startbutton1_640_large

Microsoft is preparing to revive the traditional Start button it killed with Windows 8. Sources familiar with Microsoft’s plans have revealed to The Verge that Windows 8.1 will include the return of the Start button. We understand that the button will act as a method to simply access the Start Screen, and will not include the traditional Start Menu. The button is said to look near-identical to the existing Windows flag used in the Charm bar.

Microsoft changes its mind

Microsoft’s change of heart follows another recent planned change for Windows 8.1: a boot to desktop option. We understand Microsoft will add an option to allow users to boot directly to the traditional desktop environment in future builds of the upcoming Windows 8…

Continue reading…

Read more at The Verge

Farewell, Fuduntu: The Untimely Demise of a Winning Linux Distro

They say death and taxes are the only two things that can be counted on in this life, and for those of us in the United States, last Monday delivered them both in most miserable fashion. April 15 was not only the day U.S. taxes were due, but also the day two bombs exploded at the Boston Marathon. The magnitude of that tragedy is far beyond the scope of this column, of course, but Monday also brought a casualty — albeit on a much smaller scale — to those of us here in the Linux world. It wasn’t a human death, fortunately.

Read more at LinuxInsider

13.04 Based Ubuntu Touch Arrives With Few Changes

Although billed by some as “beta”, the latest images released for Ubuntu Touch are just marking its underlying switch of foundations to the newest release on Ubuntu, 13.04. Beyond that, little has changed in the fledgling mobile operating system.

Read more at The H

The Biggest Cloud App of All: Netflix

The largest pure cloud play service of all is based on Netflix’s open-source stack running on Amazon Web Services.

Kernel Prepatch 3.9-rc8

The 3.9-rc8 prepatch is out. “Yes, I was really hoping (and originally planning) to release 3.9 final this weekend, but we had enough issues that I just didn’t feel comfy about it. It was borderline, and none of the issues were huge, and maybe I could have called this just 3.9 and opened the merge window, but hey, another week won’t hurt.

Read more at LWN