Aus Linux-Magazin 11/2010

Android-Anwendungen mit Multitouch ausstatten

© Paul Adam, Pixelio.de

Seit Version 2.0 von Android stehen allen Entwicklern auch Multitouch-Funktionen offen, die zuvor Systemanwendungen vorbehalten waren. Wer sensibel mit dem API umgeht, darf die junge Blüte berühren.

Das I-Phone 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 I-Phone 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].

Abbildung 2: Die Multitouch-Test-App erlaubt Ereignisse zu debuggen und zeigt sie dazu farbig an.

Abbildung 2: Die Multitouch-Test-App erlaubt Ereignisse zu debuggen und zeigt sie dazu farbig an.

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. (mg)

Infos

[1] Multitouch-API: [http://developer.android.com/reference/android/view/Motion.html]

[2] Beispiel-App mit Sourcecode: [http://greenrobot.de/apps/multitouch-test]

Der Autor

Markus Junginger ist seit 2007 Android-Enthusiast und -Entwickler. Sein Startup Greenrobot entwickelt mobile Software für Android. Er arbeitet seit über zehn Jahren mit Java und hat sein erstes Mobil-Projekt 2001 entwickelt.

LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben