Seit Version 2.0 von Android stehen allen Entwicklern auch Multitouch-Funktionen offen, die zuvor Systemanwendungen vorbehalten waren. Dieser Artikel zeigt, wie Sie das Feature in der Praxis einsetzen.
Das iPhone macht vor, dass Multitouch-Bedienung echte Vorteile über Spielereien hinaus haben kann. Gerade auf Smartphones sind Gesten nützlich. Kein Wunder, dass auch der Wettbewerb sich genau anschaut, was sich für die Android-Plattform nutzen lässt. Google hat das API ab Version 2.0 so erweitert, dass Entwickler und Anwender sowohl von Gesten als auch in individuellen Anwendungen von der Mehrfingertechnik profitieren.
Unterschiedliche Touchpads
Den Grad der Multitouch-Unterstützung bei Android zu betrachten ist eine wichtige Voraussetzung, um die Funktionsweise des API zu verstehen. Im Gegensatz zum iPhone kommt es hier entscheidend auf die Kombination von Hard- und Software an. Zunächst müssen Entwickler Multitouch unterstützen, was erst ab API-Level 5 (Android 2.0 oder höher) gelingt. Die verbaute Hardware beeinflusst den Grad der Unterstützung erheblich: Obwohl beispielsweise HTC sein G1 zunächst mit Android 1.0 ausgeliefert hatte, als Multitouch noch nicht einmal angekündigt war, erkannte dessen Hardware bereits einige Gesten.
Aber nicht alle Bedienflächen sind dazu in der Lage: Gerade Geräte mit resistiven Touchscreens erkennen oft nur einen Finger, etwa das HTC Tatoo. Andere Geräte liefern nur einen umschließenden Rahmen: Hier ist das Rechteck zwischen zwei Fingern bestimmbar, aber nicht zuverlässig die einzelnen Finger unabhängig voneinander.
Diese Hardwarebeschränkung illustriert Abbildung 1: Es gibt zwei unterschiedliche Positionen von zwei Fingern in einer solchen Bounding Box. Bewegen sich Finger von der einen zur anderen Position, verwechselt das Gerät oft die Fingerpositionen und kann nur noch den Rahmen eindeutig bestimmen. Viele HTC-Geräte wie das Desire verwenden diese Technologie.

Das erste Smartphone mit Android 2.0, das Motorola Milestone, erkennt die Position zweier Finger individuell. Das Galaxy S ist gar in der Lage, bis zu vier Finger unabhängig voneinander zu registrieren und zu verarbeiten. Auch aufgrund dieser Hardwarebeschränkungen ergibt sich für Entwickler in der Regel, dass sie Multitouch für Apps noch nicht voraussetzen, da sonst viele Geräte ausgeschlossen wären.
Daher vereinfacht Mutltitouch zwar optional eine App, Entwickler sollten die Technik aber nicht überall als zwingend voraussetzen. Bei Apps, für die Multitouch eine essenzielle Funktion ist, sollten sie einen Vermerk im Manifest eintragen, um sicherzustellen, dass nur Multitouch-fähige Geräte die App aus dem Android Market installieren dürfen:
<uses-feature android:name = "android.hardware.touchscreen.multitouch" />
Wer diese einschränkenden Hinweise verinnerlicht hat, darf sich dem Multitouch-API zuwenden: Es basiert auf dem Touch-API, das bereits seit Android 1.0 existiert. Die wesentliche Erweiterung bezieht sich auf das MotionEvent
, das ab Android 2.0 zusätzliche Daten mit sich führt [1]. Ansonsten dürfen Entwickler ihre Apps genauso wie für einfache Touch-Interaktion aufbauen.
Dafür nehmen sie zunächst MotionEvent
-Objekte in Empfang, wofür es unterschiedliche Möglichkeiten gibt: Typischerweise installiert der Programmierer mit setOnTouchListener()
für eine View einen Listener oder er überschreibt die Methode onTouchEvent()
der Klassen Activity
oder View
: Ein Gerüst für einen onTouchListener
zeigt Listing 1.
Listing 1
onTouchListener
01 public boolean onTouch(View v, MotionEvent event) { 02 if (event.getAction() == MotionEvent.ACTION_DOWN) { 03 int x = event.getX(); 04 int y = event.getX(); 05 doSomething(x,y); 06 } 07 }
Berühr-Ereignisse
Das Beispiel zeigt, dass der Entwickler alle relevanten Daten wie die durchgeführte Aktion oder die x- und y-Koordinate über Zugriffsmethoden im Objekt MotionEvent
erreicht. Nach einem ACTION_DOWN
-Event, das Android auslöst, wenn der Finger zum ersten Mal den Screen berührt, sendet das Betriebssystem ACTION_MOVE
-Events für Bewegungen des Fingers.
Dies geschieht so lange, bis der Anwender seinen Finger vom Screen nimmt. Das signalisiert Android der Anwendung mit ACTION_UP
. Wie die Methodensignatur von onTouch()
andeutet, lässt sich ein OnTouchListener
auf mehreren Views registrieren. Zur Unterscheidung bekommt der Entwickler das jeweils auslösende View-Objekt übergeben.
Mit dieser kleinen Auffrischung zu MotionEvents
lassen sich bereits Multitouch-Erweiterungen entwickeln. Strukturell sind keine Anpassungen an der App nötig, da alle Multitouch-Daten den gleichen Weg nehmen. Damit erhält der Code der Methode getAction()
eine weitere Bedeutung: In dem 32-Bit-Integer sind zwei Werte kodiert. Sie lassen sich über entsprechende Bitmasken und Bit-Shifts voneinander trennen:
int action = event.getAction() & MotionEvent.ACTION_MASK; int pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_ID_MASK) >> MotionEvent.ACTION_POINTER_ID_SHIFT;
Damit entspricht die Variable action
wieder dem auch ohne Mutltitouch bekannten Code. Die Variable pointerIndex
enthält einen Index auf den für das Event verantwortlichen Pointer.
Unter Pointer versteht Android an dieser Stelle einen Finger, der mit dem Touchscreen interagiert. Streng genommen ist er unter Android aber nicht auf Fingerbedienung beschränkt, sodass ein Pointer in Zukunft auch eine andere Eingabequelle sein könnte, zum Beispiel ein Mauszeiger. Jedenfalls ermitteln Programmierer über getPointerCount()
deren Anzahl. Anstelle der parameterlosen Zugriffsmethoden des MotionEvent
treten nun überladene Methoden mit jeweils dem gewünschten Pointer-Index wie beispielsweise getX(int pointerIndex)
. Je nach Anwendungsfall genügen dem Entwickler diese Informationen, um eine Multitouch-Anwendung zu entwerfen.
Mehr Freiheit für Finger
Möchte er jedoch zusätzlich einzelne Finger über einen Zeitraum hinweg verfolgen, kommen die Pointer-IDs ins Spiel. Für deren Ermittlung sorgt die Methode getPointerId()
, die den Pointer-Index als Parameter erwartet. Das folgende Beispiel veranschaulicht die Bedeutung von Pointer-IDs: Zeige- und Mittelfinger berühren nacheinander den Touchscreen. Nimmt der Anwender dann einen Finger vom Screen, können Entwickler ohne die Pointer-ID nicht feststellen, welcher Finger das war.
Der Zeigefinger bekommt in dem Beispiel die Pointer-ID 0 zugewiesen, da er der erste Pointer auf dem Screen war, während der Mittelfinger die darauf folgende Pointer-ID 1 bekommt. Anhand der IDs identifiziert der Programmierer die einzelnen Finger und bestimmt damit die verbleibenden Finger.
Die Verwendung von mehreren Pointern bringt zwei neue Action-Codes mit sich: ACTION_POINTER_DOWN
und ACTION_POINTER_UP
entsprechen ACTION_DOWN
und ACTION_UP
mit dem Unterschied, dass Android sie erst ab dem zweiten Finger einsetzt.
Ein Beispiel verdeutlicht die Abfolge von Aktionen: Zuerst berührt Finger 1 den Screen und löst eine ACTION_DOWN
aus. Berührt Finger 2 den Screen, ergibt das ein ACTION_POINTER_DOWN
-Event. Bewegen sich Finger, meldet Android ACTION_MOVE
. Verlässt Finger 1 die Glasscheibe, erhält der Code ein ACTION_POINTER_UP
-, bei Finger 2 allerdings ein ACTION_ UP
-Event.
Die App Multitouch Test
in Abbildung 2 fasst die Kernaspekte zusammen: Die im Android Market erhältliche App dient zur Visualisierung von Multitouch-Events und hält als Experimentierfeld für eigene Erweiterungen her [2].
Multitouch selbst testen
Die Funktionen der App sind überschaubar: Sie visualisiert jeden Pointer durch einen Kreis, der je Action-Code eine andere Farbe annimmt. Er wird blau, wenn der Anwender die Fläche berührt, grün, wenn er seinen Finger bewegt, oder grau, wenn er einen der Codes für das Wegnehmen auslöst. Die App zeigt auch die Pointer-ID und den letzten Action-Code über den Kreisen an. Damit bekommen Android-Entwickler schnell ein Gefühl dafür, was im Hintergrund passiert.
Den Code der App implementiert der Programmierer im Kontext einer Activity. Listing 2 zeigt die Implementierung eines OnTouchListener
, den er mittels setOnTouchListener()
an die View bindet und der in Listing 3 zu sehen ist. Aus Platzgründen sind Member-Definitionen wie beispielsweise die Arrays points[]
und lastActions[]
weggelassen. Der komplette Sourcecode ist als Download erhältlich [2].
Listing 2
onTouchListener empfängt Motion-Events
01 public boolean onTouch(View v, MotionEvent e) { 02 int action = e.getAction() & 03 MotionEvent.ACTION_MASK; 04 int pointerIndex = (e.getAction() & MotionEvent.ACTION_POINTER_ID_MASK) >> MotionEvent.ACTION_POINTER_ID_SHIFT; 05 int actionId = e.getPointerId(pointerIndex); 06 pointerCount = e.getPointerCount(); 07 if (actionId < MAX_POINTERS) { 08 lastActions[actionId] = action; 09 } 10 for (int i = 0; i < pointerCount; i++) { 11 int pointerId = e.getPointerId(i); 12 if (pointerId < MAX_POINTERS) { 13 points[pointerId] = new PointF(e.getX(i), 14 e.getY(i)); 15 if (action == MotionEvent.ACTION_MOVE) { 16 lastActions[pointerId] = action; 17 } 18 } 19 } 20 touchView.invalidate(); 21 return true; 22 }
Listing 3
Die View visualisiert die Werte
01 class TouchView extends View { 02 public TouchView(Context context) { 03 super(context); 04 setBackgroundColor(Color.WHITE); 05 } 06 07 protected void onDraw(Canvas canvas) { 08 super.onDraw(canvas); 09 for (int i = 0; i < MAX_POINTERS; i++) { 10 PointF point = points[i]; 11 if (point != null) { 12 paint.setColor(getColor(i)); 13 canvas.drawCircle(point.x, point.y, 14 radius, paint); 15 String text = getActionText(i); 16 float width = paint.measureText(text); 17 canvas.drawText(text, point.x-width/2, 18 point.y-radius-calcDevicePixels(8), 19 paint); 20 } 21 } 22 canvas.drawText("Pointer: " + pointerCount, 23 10, calcDevicePixels(30), paintInfoText); 24 } 25 }
Die ersten Zeilen in Listing 2 extrahieren den Action-Code und den Pointer-Index. Danach werden der Action-Code sowie die Pointer-Positionen in die Arrays lastAction[]
beziehungsweise points
geschrieben. Der Index beider Arrays entspricht jeweils der Pointer-ID. Die Pointeranzahl ist durch die Konstante MAX_POINTERS
limitiert: Die App verwaltet maximal 20 Pointer.
Eine Besonderheit gibt es beim Setzen der Action-Codes: So liefert das MotionEvent
bei ACTION_POINTER_DOWN
mehrere Werte, wobei sich ACTION_POINTER_DOWN
jedoch nur auf eine Pointer-ID bezieht. Nur im Falle von ACTION_MOVE
lassen sich die Werte von lastAction()
für alle im MotionEvent
enthaltenen Pointer setzen. Schließlich stößt touchView.invalidate()
in Zeile 20 ein Neuzeichnen der View an und zeigt die neuen Werte.
Die View der App ist eine eigene Klasse, die direkt von View
abgeleitet ist, wie Listing 3 zeigt. Mittels des Canvas
-Objekts lassen sich einfach die Kreise in einer Schleife zeichnen. Die dafür nötigen Koordinaten und Action-Codes hat zuvor der onTouchListener
aus Listing 2 in den Arrays points[]
und lastActions[]
gesammelt. Farbe und Text eines Kreises ergeben sich aus den Methoden getColor()
und getActionText()
. Eine Mehrfachauswahl ermittelt abhängig vom letzten Action-Code des Pointers den Rückgabewert.
Schließlich zeichnet die App noch die Anzahl der zuletzt im MotionEvent
vorhandenen Pointer als Text auf. Die Methode calcDevicePixels()
ist eine kleine Hilfsmethode für die Ermittlung einer gerätespezifischen Pixelanzahl, die von der jeweiligen Pixeldichte des Screens abhängig ist.
Das Multitouch-API unter Android 2.2 vereinfacht zwar den Code, schränkt aber die Einsatzbasis ein, da es erst wenige Geräte mit diesem Softwarestand gibt. Google hat die Wertermittlung von Action-Code und Pointer-Index vereinfacht: Bitmasken und Bit-Shifts benötigt der Entwickler nicht mehr. Dafür liefern die Methoden getActionMasked()
und getActionIndex()
gleich die gewünschten Werte aus dem MotionEvent
.
Android 2.2 hat übrigens die Konstanten ACTION_POINTER_ID_MASK
und ACTION_POINTER_ID_SHIFT
angepasst, die in den Beispielen vorkommen. Da sie sich nicht auf die ID, sondern auf den Index eines Pointers beziehen, lauten sie jetzt richtigerweise ACTION_POINTER_INDEX_MASK
sowie ACTION_POINTER_INDEX_SHIFT
.
Die vielleicht interessanteste Neuerung ist der ScaleGestureDetector
, der Pinch-and-Zoom-Gesten erkennt. Für deren Einsatz sind drei Schritte nötig: Erstens die Instanzierung eines ScaleGestureDetector
, zweitens das Weiterleiten jedes MotionEvent
an den ScaleGestureDetector
, drittens das Entgegennehmen der neuen Daten in einem speziellen Listener. Diese drei Schritte in den Methoden init()
, onTouchEvent()
und der Klasse MyScaleListener
zeigt Listing 4.
Listing 4
Pinch-and-Zoom mit dem ScaleGestureDetector
01 // beispielsweise im Konstruktor aufrufen 02 private void init(Context context) { 03 scaleDetector = new ScaleGestureDetector( 04 context, new MyScaleListener()); 05 } 06 07 public boolean onTouchEvent(MotionEvent ev) { 08 scaleDetector.onTouchEvent(ev); 09 // ... 10 } 11 private class MyScaleListener 12 extends ScaleGestureDetector.SimpleOnScaleGestureListener { 13 public boolean 14 onScale(ScaleGestureDetector detector) { 15 skalierung *= detector.getScaleFactor(); 16 invalidate(); 17 return true; 18 } 19 }
Die Methode onScale()
skaliert die View durch das Multiplizieren mit der Property scaleFactor
des ScaleGestureDetector
. Das Zentrum der Skalierung ließe sich auch mit den Methoden getFocusX()
und getFocusY()
ermitteln.
Anfassen erlaubt
Hard- und Software auf Android-Geräten üben einen großen Einfluss auf die Multitouch-Unterstützung aus. Neben der Abhängigkeit von Android 2.0 oder höher sind Touchscreens oft limitiert, was Entwickler bei der App-Entwicklung berücksichtigen sollten. Das Multitouch-API mit seinen MotionEvents
und Pointern sorgt dafür, dass Android ab 2.2 wahrlich kein Rührmichnichtan ist.
Infos
- Multitouch-API: http://developer.android.com/reference/android/view/Motion.html
- Beispiel-App mit Sourcecode: http://greenrobot.de/apps/multitouch-test