Writing multitouch applications on Android
with android-multitouch-controller

Like many things in Java/Android, writing multitouch enabled applications in Android can require quite a bit boilerplate code. It also requires quite a bit of knowledge of the workings of multitouch, and how the points are tracked by the api. This can make it tedious for new developers to get started with implementing this kind of functionality.
Much of the above can be avoided by using
Luke Hutchison's android-multitouch-controller.
It still requires
some boilerplate, however it takes care of a lot of the headaches around properly implementing operations such as pinch-to-zoom, pinch-to-scale, pinch-to-rotate, etc., and lets you get on with the rest of your app.
I've found it quite extendable, and it provides a good basis for those needing to implement a more advanced multitouch tracking. I've also made use of it extensively for my app
RoidRage, so thought I could share some examples on how to get started with using this library.
Overview:
First, grab the latest source from github:
$ git clone git://github.com/brk3/android-multitouch-controller.git
This is my fork from Luke's
original repository on Google code. It contains some extras and refinements, which I'll get into another time.
I may get round to making it into a library project but until then it's relatively easy to just drop into your own code:
$ cp -r android-multitouch-controller/src/org $MYPROJECT/src
You then need to implement the MultiTouchObjectCanvas interface from the View you want to be multitouch enabled, and override a couple of methods:
import org.metalev.multitouch.controller.MultiTouchController;
import org.metalev.multitouch.controller.MultiTouchController.MultiTouchObjectCanvas;
import org.metalev.multitouch.controller.MultiTouchController.PointInfo;
import org.metalev.multitouch.controller.MultiTouchController.PositionAndScale;
import org.metalev.multitouch.controller.MultiTouchController.MultiTouchEntity;
import org.metalev.multitouch.controller.MultiTouchController.ImageEntity;
public class DemoView extends View
implements MultiTouchObjectCanvas<MultiTouchEntity> {
private MultiTouchController<Object> multiTouchController =
new MultiTouchController<Object>(this);
public DemoView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void selectObject(MultiTouchEntity e, PointInfo touchPoint) {
}
@Override
public boolean setPositionAndScale(MultiTouchEntity e,
PositionAndScale newImgPosAndScale, PointInfo touchPoint) {
return true;
}
@Override
public void getPositionAndScale(MultiTouchEntity e,
PositionAndScale objPosAndScaleOut) {
}
@Override
public MultiTouchEntity getDraggableObjectAtPoint(PointInfo pt) {
return null;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return multiTouchController.onTouchEvent(event);
}
// rest of my code..
}
The first thing you'll probably notice about the above is the template type T given to the MultiTouchObjectCanvas.
This can be any class you want, depending on how you want to interact with your canvas. In most cases, people will probably be interested in manipulating images, so my fork of this library contains two new classes, MultiTouchEntity, and a subclass called ImageEntity.
ImageEntity is a small subclass of MultiTouchEntity, which can be used to hold an image to be managed by multitouch.
Let's see how we can add some images to our above example using ImageEntity:
import org.metalev.multitouch.controller.MultiTouchController;
import org.metalev.multitouch.controller.MultiTouchController.MultiTouchObjectCanvas;
import org.metalev.multitouch.controller.MultiTouchController.PointInfo;
import org.metalev.multitouch.controller.MultiTouchController.PositionAndScale;
import org.metalev.multitouch.controller.MultiTouchController.MultiTouchEntity;
import org.metalev.multitouch.controller.MultiTouchController.ImageEntity;
public class DemoView extends View
implements MultiTouchObjectCanvas<MultiTouchEntity> {
private MultiTouchController<MultiTouchEntity> multiTouchController =
new MultiTouchController<MultiTouchEntity>(this);
private ArrayList<MultiTouchEntity> mImages =
new ArrayList<MultiTouchEntity>();
private static final int[] IMAGES = { R.drawable.m74hubble, R.drawable.catarina,
R.drawable.tahiti, R.drawable.sunset, R.drawable.lake };
public DemoView(Context context, AttributeSet attrs) {
super(context, attrs);
Resources res = context.getResources();
for (int i = 0; i < IMAGES.length; i++) {
mImages.add(new ImageEntity(IMAGES[i], res));
mImages.get(i).load(context, 50.0f, 50.0f);
}
}
@Override
public void selectObject(MultiTouchEntity e, PointInfo touchPoint) {
if (img != null) {
// Move image to the top of the stack when selected
mImages.remove(img);
mImages.add(img);
} else {
// Called with img == null when drag stops.
}
invalidate();
}
@Override
public boolean setPositionAndScale(MultiTouchEntity e,
PositionAndScale newImgPosAndScale, PointInfo touchPoint) {
boolean ok = ((ImageEntity)img).setPos(newImgPosAndScale);
if (ok)
invalidate();
return ok;
}
@Override
public void getPositionAndScale(MultiTouchEntity e,
PositionAndScale objPosAndScaleOut) {
objPosAndScaleOut.set(img.getCenterX(), img.getCenterY(),
(mUIMode & UI_MODE_ANISOTROPIC_SCALE) == 0,
(img.getScaleX() + img.getScaleY()) / 2,
(mUIMode & UI_MODE_ANISOTROPIC_SCALE) != 0,
img.getScaleX(), img.getScaleY(),
(mUIMode & UI_MODE_ROTATE) != 0, img.getAngle());
}
@Override
public MultiTouchEntity getDraggableObjectAtPoint(PointInfo pt) {
float x = pt.getX(), y = pt.getY();
int n = mImages.size();
for (int i = n - 1; i >= 0; i--) {
ImageEntity im = (ImageEntity) mImages.get(i);
if (im.containsPoint(x, y))
return im;
}
return null;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return multiTouchController.onTouchEvent(event);
}
// rest of my code..
}
Again for simple purposes it's possible that the amount of boilerplate could be stripped down further, but if you have a read through it's fairly self explanatory.
The above code has been mostly extracted from the demo application that ships with the library, 'MTPhotoSorter', which I recommend at least building and installing this to get a feel for what's possible with the library.
That's about it for now, I can follow up this post with a few more tips and examples if anyone finds it useful.