Eine der am weitesten verbreiteten Veränderungen bei der Verwendung von Touchscreen-Geräten in den letzten Jahren war die Einführung von Fingergesten wie Wischen und Schleudern. Sie ermöglichen eine sehr intuitive und natürliche Interaktion zwischen Benutzer und Gerät. In diesem Tutorial erfahren Sie, wie Sie Gesten in Ihren eigenen Android-Anwendungen verwenden.
In diesem Tutorial wird Code verwendet, in einem Open Source-Projekt wird wird. Die Autoren gehen davon aus, dass der Leser Erfahrung mit Android und Java hat. Wenn Sie alle Fragen zu unseren eigenen haben, können Sie diese gerne stellen.
In diesem Tutorial erfahren Sie, wie Sie mit Fingergesten in Ihren Anwendungen umgehen. Dazu verwenden wir eine einfache Canvas-Objektzeichnung für ein benutzerdefiniertes Ansichtsobjekt. Diese Technik kann auf jede grafische Umgebung angewendet werden, die Sie verwenden, sei es eine 2D-Oberfläche oder sogar ein OpenGL ES-Rendering. Wenn Sie an Multitouch-Gesten interessiert sind (ein fortgeschritteneres Thema für die Gestenbehandlung), werden wir dies in einem weiteren Tutorial behandeln.
Schritt 0: Erstellen des Projekts
Fangen wir einfach an. Erstellen Sie ein neues Android-Projekt. Wir haben unser Projekt Gesture Fun benannt und seine einzige Aktivität mit dem Namen GestureFunActivity konfiguriert. Ändern Sie die Standard-Layoutdatei main.xml in das folgende, sehr einfache Layout:
Das wichtige Element in diesem Layout ist die FrameLayout-Definition. Das FrameLayout-Steuerelement wird verwendet, um die benutzerdefinierte Ansicht zu speichern, in der ein Bild gezeichnet wird.
Zuletzt aktualisieren wir die onCreate()-Methode der Activity-Klasse, um das FrameLayout-Steuerelement zu initialisieren und ihm einige Inhalte bereitzustellen:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
FrameLayout frame = (FrameLayout) findViewById(R.id.graphics_holder);
PlayAreaView image = new PlayAreaView(this);
frame.addView(image);
}
Zu diesem Zeitpunkt haben wir die PlayAreaView-Klasse noch nicht definiert, sodass das Projekt nicht kompiliert werden kann. Haben Sie Geduld, wir werden im nächsten Schritt darauf zurückkommen.
Schritt 1: Erstellen einer neuen Ansicht: PlayAreaView
Nachdem alle unsere Projekteinrichtungen aus dem Weg geräumt sind, können wir uns jetzt auf den interessanten Teil konzentrieren: das Zeichnen auf dem Canvas-Objekt. Eine einfache Möglichkeit, ein Canvas-Objekt zum Zeichnen zu bringen, besteht darin, die onDraw() -Methode eines View-Objekts zu überschreiben. Praktischerweise hat diese Methode einen einzigen Parameter: das Canvas-Objekt. Das Zeichnen einer Bitmap-Grafik auf einem Canvas-Objekt ist so einfach wie das Aufrufen der drawBitmap() -Methode des Canvas-Objekts. Hier ist ein einfaches Beispiel für eine Implementierung der onDraw() -Methode, wie sie in unserer neuen PlayAreaView-Klasse definiert ist:
Unsere Implementierung der onDraw()-Methode ist ziemlich einfach. Wie üblich müssen Sie Ihre DEBUG_TAG-Protokollierungs-Tag-Variable irgendwo in Ihrer Aktivität definieren. Der größte Teil der onDraw() -Methode ist nur eine Informationsausgabe. Die einzige echte Arbeit, die mit dieser Methode ausgeführt wird, findet im Aufruf drawBitmap() statt, wobei der erste Parameter das zu zeichnende Bild ist. Der zweite Parameter ist ein Matrix-Objekt namens translate, das, wie der Name schon sagt, bestimmt, wo die Bitmap relativ zu der Ansicht gezeichnet wird, in der sich das Canvas-Objekt befindet. Der gesamte Rest des Codes in diesem Tutorial umfasst das Bearbeiten der Übersetzungsmatrix basierend auf bestimmten Benutzerberührungsereignissen. Das ändert sich wiederum, wo das Bitmap-Objekt im Canvas-Bereich und damit auf dem Bildschirm gezeichnet wird.
Schritt 2: Konfigurieren der neuen Ansicht
Die PlayAreaView-Klasse benötigt einen Konstruktor, um eine Ersteinrichtung durchzuführen. Da unsere benutzerdefinierte Ansicht auf Gesten reagieren muss, benötigen wir hier einen GestureDetector. Ein GestureDetector ist eine Android-Klasse, die Bewegungsereignisse aufnehmen, mathematisch zaubern kann, um festzustellen, was sie sind, und dann Aufrufe als bestimmte Geste oder andere Bewegungsrückrufe an ein GestureListener-Objekt delegieren kann. Das GestureListener-Objekt, eine von uns implementierte Klasse, empfängt diese Aufrufe für bestimmte Gesten, die der GestureDetector erkennt, und ermöglicht es uns, nach Belieben darauf zu reagieren (in diesem Fall, um eine Grafik in unserer PlayAreaView zu verschieben). Obwohl der GestureDetector die Erkennung bestimmter Bewegungen übernimmt, führt er weder etwas Spezifisches mit ihnen aus, noch verarbeitet er alle Arten von Gesten. Für die Zwecke dieses Tutirials werden jedoch gerade genug Informationen bereitgestellt. Lassen Sie es uns also verbinden:
public PlayAreaView(Context context) {
super(context);
translate = new Matrix();
gestures = new GestureDetector(GestureFunActivity.this,
new GestureListener(this));
droid = BitmapFactory.decodeResource(getResources(),
R.drawable.droid_g);
}
Schauen wir uns den PlayAreaView-Konstruktor etwas genauer an. Zuerst initialisieren wir die Übersetzungsmatrix in eine Identitätsmatrix (Standardeinstellung). Denken Sie daran, dass eine Identitätsmatrix keine Änderungen an einer Bitmap vornimmt: Sie wird an ihrer ursprünglichen Position gezeichnet. Als Nächstes erstellen und initialisieren wir den GestureDetector - einen Standard - und weisen ihm ein gültiges GestureListener-Objekt zu (wir werden gleich mehr darüber sprechen). Schließlich wird die als Droide bezeichnete Bitmap-Zeichnung direkt aus den Projektressourcen geladen. Sie können jedes gewünschte Bild verwenden - einen Baseball, einen Apfel, einen Glückskeks usw. Das ist die Grafik, die Sie auf dem Canvas-Objekt herumwerfen werden.
Schritt 3: Anschließen des GestureDetector
Als nächstes gelangen wir zum GestureListener, da es sich um ein benutzerdefiniertes Objekt handelt. Lassen Sie uns zunächst das GestureDetector-Objekt so verkabeln, dass es die Bewegungsdaten empfängt, die für die Erkennung von Magie erforderlich sind.
Verbinden wir nun das GestureDector-Objekt mit dem Namen "Gesten", um Ereignisse zu empfangen. Überschreiben Sie dazu die onTouchEvent()-Methode des View-Steuerelements in der PlayAreaView-Klasse wie folgt:
@Override
public boolean onTouchEvent(MotionEvent event) {
return gestures.onTouchEvent(event);
}
Wir haben hier den GestureDetector zum letzten Wort in allen Berührungsereignissen für diese benutzerdefinierte Ansicht gemacht. Der GestureDetector macht jedoch nichts mit Bewegungsereignissen, sondern erkennt sie einfach und ruft die registrierte GestureListener-Klasse auf.
Schritt 4: Implementieren eines Gesten-Listeners
Um auf die von der GestureDetector-Klasse erkannten Ereignisse reagieren zu können, müssen wir die GestureListener-Klasse implementieren. Die Bewegungsereignisse, an denen wir am meisten interessiert sind, sind Doppelklicks und Gesten jeglicher Art. Um auf diese Art von Bewegungsereignissen zu warten, muss unsere GestureListener-Klasse sowohl die OnGestureListener- als auch die OnDoubleTapListener-Schnittstelle implementieren.
private class GestureListener implements GestureDetector.OnGestureListener,
GestureDetector.OnDoubleTapListener {
PlayAreaView view;
public GestureListener(PlayAreaView view) {
this.view = view;
}
}
Fügen Sie nach dem Hinzufügen dieser Klasse als Unterklasse der Aktivität Standardimplementierungen für jede der erforderlichen Methoden hinzu. Hier ist beispielsweise eine Implementierung für die onDown() -Methode:
Durch die Implementierung dieser Methoden können Sie die verschiedenen Ereignisse untersuchen, die vom GestureDetector-Objekt erkannt werden. Interessanterweise wird die Hauptgeste, an der wir hier interessiert sind - Scrollen (oder Ziehen) - nicht erkannt, wenn die onDown()-Methode nicht true zurückgibt. Sie können jedoch für die anderen erkannten Ereignisse, an denen Sie nicht interessiert sind, false zurückgeben.
Das MotionEvent-Objekt, das als Parameter an jede Rückrufmethode übergeben wird, repräsentiert manchmal das Berührungsereignis, das die Gestenerkennung gestartet hat, und manchmal das letzte Ereignis, das die Gestenerkennung abgeschlossen hat. Für unsere Zwecke lassen wir die GestureDetector-Klasse alle Details der Entschlüsselung verarbeiten, welche Art von Bewegung das MotionEvent darstellt.
Hinweis: Das Android-Framework bietet auch eine Convenience-Klasse namens SimpleOnGestureListener, die die beiden Schnittstellen (OnGestureListener & OnDoubleTapListener) zu einer einzigen Klasse mit Standardimplementierungen für alle Methoden kombiniert. Die Standardimplementierungen geben false zurück.
Das erste Ereignis, das wir behandeln möchten, ist das Bildlaufereignis. Ein Bildlaufereignis tritt auf, wenn der Benutzer den Bildschirm berührt und dann seinen Finger darüber bewegt. Diese Geste wird auch als Drag-Ereignis bezeichnet. Dieses Ereignis tritt über die onScroll() -Methode der OnGestureListener-Schnittstelle ein.
Hier ist die Implementierung der onScroll()-Methode:
Verwenden Sie das Bildlaufereignis, um eine Verschiebungsanforderung an das PlayAreaView-Objekt weiterzuleiten. Die Implementierung dieser Methode ist ein wichtiger erster Schritt bei der Abbildung, wie ein Fingerbewegungsereignis die grafische Bewegung auffordert. Wir werden in Kürze darauf zurückkommen. In der Zwischenzeit haben Sie Ihre erste Geste ausgeführt!
Die Grafik wird über den gesamten Bildschirm verschoben - und manchmal sogar davon entfernt. Per Definition ist das Bild nur sichtbar, wenn es innerhalb der Grenzen des View-Objekts gezeichnet wird. Wenn die Koordinaten der Grafiken außerhalb der Grenzen des Ansichtsobjekts landen, wird die Grafik abgeschnitten (nicht sichtbar). Sie können die Kantenerkennung und verschiedene andere logische Elemente einfügen (wir befürchten, dass dies nicht in diesem Tutorial enthalten ist) oder einfach die Erkennung für doppeltes Tippen hinzufügen und die Position der Grafik zurücksetzen. Hier ist die Beispielimplementierung der onDoubleTap()-Methode (über die OnDoubleTapListener-Schnittstelle):
Wie bei den möglichen Methodenimplementierung werden wir diese diese Bewegung, ein doppeltes Tippen, um eine Änderung in unserer Ansichtssteuerung auszulösen. In diesem Herbst setzen wir einfach den Speicherort der Ansicht zurück.
Schritt 6: Schleudern
Eine Schleudergeste hinterlässt im Wesentlichen die Geschwindigkeit eines Objekts, das über einen Bildschirm gezogen wurde. Das in Bewegung befindliche Element wird normalerweise allmählich verlangsamt. Dieses Verhalten wird jedoch von der Implementierung des Entwicklers bestimmt. In einem Spiel könnte die Geschwindigkeit beispielsweise der Physik der Spielwelt unterliegen. In anderen Anwendungen könnte die Geschwindigkeit auf der Formel basieren, die sich für die Aktion, die sie darstellt, als richtig anfühlt. Testen ist der beste Weg, um eine Vorstellung davon zu bekommen, wie es sich anfühlt. Nach unserer Erfahrung sind einige Versuche und Irrtümer erforderlich, um sich auf etwas zu einigen, das sich genau richtig anfühlt - und aussieht.
In unserer Implementierung werden wir die Zeitspanne variieren, bevor die durch das Schleudern verursachte Bewegung stoppt, und dann einfach eine Animation des Bildes zum endgültigen Ziel starten, basierend auf der Geschwindigkeit, die uns von der onFling() -Methode und übergeben wurde die Zeit, die wir einstellen. Denken Sie daran, dass die Schleudergeste erst erkannt wird, nachdem der Finger des Benutzers das Display nicht mehr berührt. Stellen Sie sich vor, Sie werfen einen Stein - der Stein bewegt sich weiter, wenn Sie ihn loslassen - das ist der Teil, den wir animieren möchten, sobald der Benutzer „loslässt“.
Klingt komplex? Hier ist der Code:
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2,
final float velocityX, final float velocityY) {
Log.v(DEBUG_TAG, "onFling");
final float distanceTimeFactor = 0.4f;
final float totalDx = (distanceTimeFactor * velocityX/2);
final float totalDy = (distanceTimeFactor * velocityY/2);
view.onAnimateMove(totalDx, totalDy,
(long) (1000 * distanceTimeFactor));
return true;
}
Wir müssen nicht einmal die beiden MotionEvent-Parameter untersuchen, die Geschwindigkeitsdaten sind für unsere Zwecke ausreichend. Die Geschwindigkeitseinheiten sind in Pixel pro Sekunde angegeben. Wir können also die Geschwindigkeitsdaten verwenden, um einen Skalierungsfaktor zu bestimmen, der verwendet wird, um die endgültige Zeitdauer zu bestimmen, bevor das Bild vollständig zur Ruhe kommt. In unserem Fall verwenden wir 40% einer Sekunde (400 ms). Wenn wir also die Hälfte der beiden Geschwindigkeitswerte mit 40% multiplizieren (auch bekannt als die Variable distanceTimeFactor), erhalten wir die Gesamtbewegung, die nach diesem Sekundenbruchteil erreicht wird. Schließlich geben wir diese Informationen an unsere benutzerdefinierte onAnimateMove()-Methode des View-Objekts weiter, wodurch unsere Grafik mithilfe der vom fling-Bewegungsereignis bereitgestellten Informationen tatsächlich über den Bildschirm verschoben wird.
Warum die Hälfte der Anfangsgeschwindigkeit? Wenn wir beispielsweise mit Geschwindigkeit A beginnen und über einen beliebigen Zeitraum mit Geschwindigkeit B enden, beträgt die Durchschnittsgeschwindigkeit (A+B)/2. In diesem Fall ist die Endgeschwindigkeit 0. Daher halbieren wir die Geschwindigkeit hier, damit das Bild nicht versehentlich so aussieht, als würde es schneller von unserem Finger wegspringen als vor dem Loslassen.
Warum 400 ms? Kein Grund, aber auf den meisten Geräten sieht es einigermaßen gut aus. Es ist nicht unähnlich, Ihre Mausbewegungen zu kalibrieren - zu schnell und es fühlt sich nervös und schwer zu sehen an, zu langsam und Sie warten darauf, dass Ihr träger Mauszeiger Ihr Gehirn einholt. Dieser Wert ist die Hauptvariable, die Sie an das „Gefühl“ Ihres Flings anpassen können. Je höher der Wert, desto weniger „Reibung“ scheint das Bild zu haben, wenn es auf dem Bildschirm zum Stillstand kommt. Wenn Sie echte Oberflächenvariationen haben, müssen Sie regelmäßige physikalische Berechnungen anwenden. Hier machen wir nur eine feste Funktion, die ohne echte Physik langsamer wird.
Schritt 7: Verschieben des Bildes
Nachdem alle Gesten, die uns interessieren, behandelt wurden, ist es an der Zeit, die tatsächliche Bewegung der zugrunde liegenden Grafik zu implementieren. Fügen Sie in der PlayAreaView-Klasse die onMove()-Methode hinzu:
Diese Methode macht zwei Dinge. Erstens übersetzt es (translate = Grafikbegriff für die Bewegung von Punkt A nach Punkt B) unsere eigene Matrix um die Entfernung, um die sich der Finger bewegt hat. Dann macht es die Ansicht ungültig, so dass sie neu gezeichnet wird. Beim Zeichnen wird das Bild an einer anderen Stelle in der Ansicht gezeichnet. Wenn wir das gesamte View-Objekt schwenken wollten, könnten wir die translate()-Methode des View-Objekts verwenden, um die interne Matrix zu aktualisieren, die von Canvas zum Ausführen der gesamten Zeichnung verwendet wird. Dies mag für einige Dinge gut funktionieren, aber wenn wir statische (womit feste, stationäre, unbewegliche, wie Berge) Dinge in der Ansicht hätten, wäre dies nicht der Fall. Stattdessen aktualisieren wir in diesem Fall einfach unsere eigene Matrix, übersetzen, die wir jedes Mal verwenden, wenn wir die Grafik zeichnen.
Jetzt fügen wir auch die onResetLocation()-Methode hinzu:
public void onResetLocation() {
translate.reset();
invalidate();
}
Diese Methode setzt die Matrix einfach auf die Identitätsmatrix zurück und bewirkt, dass die Ansicht über die invalidate()-Methode erneut gezeichnet wird. Wenn die Ansicht neu gezeichnet wird, befindet sich die Grafik wieder in ihrer ursprünglichen Position.
Schritt 8: Reibungsloses Verschieben des Bildes
Für die Schleuderbewegung haben wir etwas mehr zu tun, als sie nur an einer neuen Stelle zu zeichnen. Wir möchten, dass es diese Position reibungslos animiert. Eine reibungslose Bewegung kann durch Animation erreicht werden, dh das Bild wird sehr schnell an einer anderen Stelle gezeichnet. Android verfügt über integrierte Animationsklassen, die jedoch für ganze Ansichten gelten. Wir animieren kein Ansichtsobjekt. Stattdessen verschieben wir ein Bild auf der Leinwand, das von einer Ansicht gesteuert wird. Also müssen wir unsere eigene Animation implementieren. Verflixt. ☺
Android bietet verschiedene Interpolatoren, mit denen Sie die Position eines Objekts zu einem bestimmten Zeitpunkt während Animationen mithilfe der integrierten Animationsklassen optimieren können. Wir können diese verschiedenen Interpolatoren in unserer eigenen Animation nutzen, um ein wenig Arbeit zu sparen - und einige lustige Effekte anwenden. Das ist möglich, da die bereitgestellten Interpolatoren sehr allgemein gehalten sind und nicht an die Besonderheiten der integrierten View-Animationen gebunden sind.
Beginnen wir mit der onAnimateMove()-Methode:
private Matrix animateStart;
private Interpolator animateInterpolator;
private long startTime;
private long endTime;
private float totalAnimDx;
private float totalAnimDy;
public void onAnimateMove(float dx, float dy, long duration) {
animateStart = new Matrix(translate);
animateInterpolator = new OvershootInterpolator();
startTime = System.currentTimeMillis();
endTime = startTime + duration;
totalAnimDx = dx;
totalAnimDy = dy;
post(new Runnable() {
@Override
public void run() {
onAnimateStep();
}
});
}
Bei dieser Methode verfolgen wir den Startort, die Startzeit und die Endzeiten.
Hier initialisieren wir unsere Animation mit der OvershootInterpolator-Klasse. Da dieser Interpolator bewirkt, dass sich das Bild insgesamt etwas weiter bewegt als von uns berechnet, beginnt das Bild technisch etwas schneller. Der Unterschied ist so gering, dass er nicht bemerkt werden sollte. Wenn Sie jedoch Präzisionsgenauigkeit anstreben, müssen Sie sich darauf einstellen (was bedeuten würde, dass Sie Ihren eigenen Interpolator schreiben. über den Rahmen dieses Tutorials hinaus - und über eine Methode zur Berechnung der zurückgelegten Gesamtstrecke verfügen).
Alle diese Informationen werden verwendet, um zu bestimmen, wann (rechtzeitig) wir uns entlang der Gesamtdauer der Animation befinden. Wir verwenden diese Daten, um zu bestimmen, wann (in Prozent der Zeit) diese Informationen an den Interpolator übergeben werden sollen. Der Interpolator benötigt dies, damit er uns sagen kann, wo (in Prozent Entfernung) wir vom Startpunkt bis zum Endpunkt unserer Bewegung sind. Wir verwenden dies wiederum, um zu bestimmen, wo (in Pixel) wir vom Ausgangspunkt sind.
Diese Berechnung erfolgt in der unten gezeigten Methode onAnimateStep(). Wir rufen die onAnimationStep() -Methode über einen Beitrag in der Nachrichtenwarteschlange auf. Wir möchten nicht in eine enge Schleife geraten - wir würden dazu führen, dass das System nicht mehr reagiert. Ein einfacher Weg ist es also, einfach die Nachrichten zu posten. Auf diese Weise kann das System durch asynchrones Verhalten reaktionsschnell bleiben, ohne sich mit Threads befassen zu müssen. Da wir ohnehin auf dem UI-Thread zeichnen müssen, hat ein Thread in diesem einfachen Beispiel wenig Sinn.
Implementieren wir nun die onAnimateStep()-Methode:
First, we determine percentage of time that the animation has gone through, stored in the float variable percentTime. Then we use that data so the interpolator can tell us where we are as a percentage from start to end, stored as the float variable called percentDistance. Then, we use that data to determine where we are in pixels along both the x and y axis from the starting position, stored as curDx and curDy (standing for current delta x and current delta y). The translation matrix is then reset to the initial value we stored (animateStart). Finally, the onMove() method is used with the calculated curDx and curDy to actually move the graphic to it’s next position along the animation. Whew!
The interpolator does not give the distance changed from the previous location. Instead, it only gives the current percent complete of the total travelled distance. This is why we use our known starting location. Finally, if it is not done yet, post a call to onAnimateStep().
Note: Consider exploring the other interpolators provided with the Android framework. The Linear Interpolator just slows down motion evenly over the distance. This would produce the exact total distance travelled that we calculated. Try it out and see how it differs from the Overshoot Interpolator.
Step 9: Playing with the Result
That’s all there is to it. If you’ve been following along with your own code, you might verify you entered everything by looking out our open source code. Otherwise, when you run it, your image will animate something like what you see in this video:
In this tutorial, you’ve learned how to connect up a GestureDetector to a custom View to smoothly move and animate an image within it on a Canvas. In doing so, you’ve learned how to handle basic gestures such as fling, as well as how to implement a custom View. In a future tutorial, you’ll learn how to add multitouch gestures for some other interesting effects.
Mobile developers Lauren Darcey and Shane Conder have coauthored numerous books on Android development. Our latest books include Sams Teach Yourself Android Application Development in 24 Hours (3rd Edition), Introduction to Android Application Development: Android Essentials (4th Edition), and Advanced Android Application Development (4th Edition). Lauren and Shane run a boutique consulting firm specializing in the development of commercial-grade Android applications for smartphones, tablets, wearables (i.e. Google Glass), and more. They can be reached on Google+, their blog at androidbook.blogspot.com and on Twitter @androidwireless.
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.