Benutzerdefinierte Ansicht interaktiv gestalten

Schreiben Sie jetzt
Jetpack Compose ist das empfohlene UI-Toolkit für Android. Hier erfahren Sie, wie Sie in „Compose“ mit Layouts arbeiten.

Das Zeichnen einer Benutzeroberfläche ist nur ein Teil der Erstellung einer benutzerdefinierten Ansicht. Außerdem müssen Sie dafür sorgen, dass Ihre Ansicht auf Nutzereingaben so reagiert, dass sie der realen Aktion, die Sie nachahmen, sehr ähnlich ist.

Achten Sie darauf, dass sich die Objekte in Ihrer App wie echte Objekte verhalten. Achten Sie beispielsweise darauf, dass Bilder in Ihrer App nicht aus der App hervorgehen und an anderer Stelle wieder angezeigt werden. Dies ist bei Objekten in der realen Welt nicht der Fall. Verschieben Sie Ihre Bilder stattdessen von einem Ort an einen anderen.

Nutzer wahrnehmen sogar subtiles Verhalten oder Gefühl in einer Benutzeroberfläche und reagieren am besten auf Feinheiten, die die reale Welt imitieren. Wenn Nutzer beispielsweise mit einem UI-Objekt losfliegen, vermitteln Sie ihnen zu Beginn ein Gefühl der Trägheit, die die Bewegung verzögert. Vermitteln Sie ihnen am Ende der Bewegung einen Impuls, durch den das Objekt über den Schlauch hinaus transportiert wird.

Auf dieser Seite wird beschrieben, wie Sie mithilfe der Funktionen des Android-Frameworks diese realen Verhaltensweisen zu Ihrer benutzerdefinierten Ansicht hinzufügen können.

Weitere Informationen finden Sie unter Eingabeereignisse und Übersicht über Property-Animation.

Touch-Gesten zur Eingabe

Wie viele andere UI-Frameworks unterstützt Android ein Eingabeereignismodell. Nutzeraktionen werden zu Ereignissen, die Callbacks auslösen. Sie können diese Callbacks überschreiben, um anzupassen, wie Ihre App auf den Nutzer reagiert. Das häufigste Eingabeereignis im Android-System ist touch, das onTouchEvent(android.view.MotionEvent) auslöst. Überschreiben Sie diese Methode, um das Ereignis so zu verarbeiten:

Kotlin

override fun onTouchEvent(event: MotionEvent): Boolean {
    return super.onTouchEvent(event)
}

Java

@Override
   public boolean onTouchEvent(MotionEvent event) {
    return super.onTouchEvent(event);
   }

Berührungsereignisse an sich sind nicht besonders nützlich. Moderne Touch-UIs definieren Interaktionen in Form von Gesten wie Tippen, Ziehen, Drücken, Ziehen und Zoomen. Zum Umwandeln von unbearbeiteten Touch-Ereignissen in Gesten bietet Android GestureDetector.

Erstellen Sie ein GestureDetector, indem Sie eine Instanz einer Klasse übergeben, die GestureDetector.OnGestureListener implementiert. Wenn Sie nur wenige Gesten verarbeiten möchten, können Sie GestureDetector.SimpleOnGestureListener erweitern, anstatt die GestureDetector.OnGestureListener-Oberfläche zu implementieren. Mit diesem Code wird beispielsweise eine Klasse erstellt, die GestureDetector.SimpleOnGestureListener erweitert und onDown(MotionEvent) überschreibt.

Kotlin

private val myListener =  object : GestureDetector.SimpleOnGestureListener() {
    override fun onDown(e: MotionEvent): Boolean {
        return true
    }
}

private val detector: GestureDetector = GestureDetector(context, myListener)

Java

class MyListener extends GestureDetector.SimpleOnGestureListener {
   @Override
   public boolean onDown(MotionEvent e) {
       return true;
   }
}
detector = new GestureDetector(getContext(), new MyListener());

Unabhängig davon, ob Sie GestureDetector.SimpleOnGestureListener verwenden oder nicht, müssen Sie immer eine onDown()-Methode implementieren, die true zurückgibt. Das ist erforderlich, da alle Touch-Gesten mit einer onDown()-Nachricht beginnen. Wenn du false von onDown() zurückgibst, wie es bei GestureDetector.SimpleOnGestureListener der Fall ist, geht das System davon aus, dass du den Rest der Geste ignorieren möchtest. Die anderen Methoden von GestureDetector.OnGestureListener werden dann nicht aufgerufen. Geben Sie false nur dann von onDown() zurück, wenn Sie eine ganze Geste ignorieren möchten.

Nachdem du GestureDetector.OnGestureListener implementiert und eine Instanz von GestureDetector erstellt hast, kannst du mit GestureDetector die Touch-Ereignisse interpretieren, die du in onTouchEvent() erhältst.

Kotlin

override fun onTouchEvent(event: MotionEvent): Boolean {
    return detector.onTouchEvent(event).let { result ->
        if (!result) {
            if (event.action == MotionEvent.ACTION_UP) {
                stopScrolling()
                true
            } else false
        } else true
    }
}

Java

@Override
public boolean onTouchEvent(MotionEvent event) {
   boolean result = detector.onTouchEvent(event);
   if (!result) {
       if (event.getAction() == MotionEvent.ACTION_UP) {
           stopScrolling();
           result = true;
       }
   }
   return result;
}

Wenn du onTouchEvent() ein Touch-Ereignis übergibst, das nicht als Teil einer Geste erkannt wird, wird false zurückgegeben. Anschließend können Sie Ihren eigenen benutzerdefinierten Code für die Gestenerkennung ausführen.

Physisch plausible Bewegungen erzeugen

Gesten sind eine leistungsstarke Möglichkeit, Geräte mit Touchscreen zu steuern. Sie sind jedoch unlogisch und schwer zu merken, sofern sie nicht zu physisch plausiblen Ergebnissen führen.

Angenommen, Sie möchten eine horizontale Fingerbewegung implementieren, mit der sich das gezeichnete Element in der Ansicht um seine vertikale Achse dreht. Diese Geste ist sinnvoll, wenn die UI reagiert, indem sie sich schnell in Richtung des Schlägers bewegt und dann langsamer wird, als ob der Nutzer auf einen Schwung drückt und ihn dreht.

In der Dokumentation zum Animieren einer Scrollbewegung wird ausführlich erläutert, wie Sie Ihr eigenes Scoll-Verhalten implementieren können. Den Eindruck eines Schwungrads zu simulieren, ist jedoch nicht ganz einfach. Damit ein Schwungradmodell richtig funktioniert, ist eine Menge Physik und Mathematik erforderlich. Zum Glück bietet Android Hilfsklassen, mit denen dieses und andere Verhaltensweisen simuliert werden können. Die Klasse Scroller bildet die Grundlage für die Verarbeitung von Fliege-Gesten im Flywheel-Stil.

Um einen Fling zu starten, rufen Sie fling() mit der Startgeschwindigkeit und den Mindest- und Höchstwerten x und y auf. Für den Geschwindigkeitswert können Sie den von GestureDetector berechneten Wert verwenden.

Kotlin

fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
    scroller.fling(
            currentX,
            currentY,
            (velocityX / SCALE).toInt(),
            (velocityY / SCALE).toInt(),
            minX,
            minY,
            maxX,
            maxY
    )
    postInvalidate()
    return true
}

Java

@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
   scroller.fling(currentX, currentY, velocityX / SCALE, velocityY / SCALE, minX, minY, maxX, maxY);
   postInvalidate();
    return true;
}

Durch den Aufruf von fling() wird das Physikmodell für die Touch-Geste eingerichtet. Aktualisieren Sie danach Scroller, indem Sie in regelmäßigen Abständen Scroller.computeScrollOffset() aufrufen. computeScrollOffset() aktualisiert den internen Status des Scroller-Objekts. Dazu wird die aktuelle Zeit gelesen und das Physikmodell verwendet, um die x- und y-Position zu diesem Zeitpunkt zu berechnen. Rufen Sie getCurrX() und getCurrY() auf, um diese Werte abzurufen.

Bei den meisten Ansichten werden die x- und y-Positionen des Scroller-Objekts direkt an scrollTo() übergeben. Dieses Beispiel ist etwas anders: Es wird die aktuelle Scroll-x-Position verwendet, um den Drehwinkel der Ansicht festzulegen.

Kotlin

scroller.apply {
    if (!isFinished) {
        computeScrollOffset()
        setItemRotation(currX)
    }
}

Java

if (!scroller.isFinished()) {
    scroller.computeScrollOffset();
    setItemRotation(scroller.getCurrX());
}

Die Klasse Scroller berechnet Scrollpositionen für Sie, wendet diese Positionen jedoch nicht automatisch auf Ihre Ansicht an. Wenden Sie oft so viele neue Koordinaten an, dass die Scrollanimation flüssiger aussieht. Dafür gibt es zwei Möglichkeiten:

  • Erzwingen Sie das erneute Zeichnen, indem Sie nach dem Aufrufen von fling() postInvalidate() aufrufen. Bei dieser Technik müssen Sie den Scroll-Offset in onDraw() berechnen und jedes Mal postInvalidate() aufrufen, wenn sich der Scroll-Offset ändert.
  • Richten Sie eine ValueAnimator ein, die für die Dauer des Fling-Vorgangs animiert wird, und fügen Sie einen Listener hinzu, um Animationsaktualisierungen zu verarbeiten. Rufen Sie dazu addUpdateListener() auf. Mit diesem Verfahren lassen sich die Eigenschaften einer View animieren.

Reibungslose Übergänge

Nutzer erwarten, dass eine moderne Benutzeroberfläche reibungslos zwischen den Zuständen wechselt: UI-Elemente werden ein- und ausgeblendet, anstatt zu erscheinen und zu verschwinden. Bewegungen beginnen und enden reibungslos, statt abrupt zu starten und anzuhalten. Mit dem Android-Framework für Animationen lassen sich reibungslose Übergänge einfacher gestalten.

Wenn Sie das Animationssystem verwenden möchten, sollten Sie die Eigenschaft nicht direkt ändern, wenn sich eine Eigenschaft ändert, was sich auf die Darstellung Ihrer Ansicht auswirkt. Verwenden Sie stattdessen ValueAnimator, um die Änderung vorzunehmen. Im folgenden Beispiel wird durch das Ändern der ausgewählten untergeordneten Komponente in der Ansicht die gesamte gerenderte Ansicht so gedreht, dass der Auswahlzeiger zentriert wird. ValueAnimator ändert die Rotation über einen Zeitraum von mehreren Hundert Millisekunden, anstatt den neuen Rotationswert sofort festzulegen.

Kotlin

autoCenterAnimator = ObjectAnimator.ofInt(this, "Rotation", 0).apply {
    setIntValues(targetAngle)
    duration = AUTOCENTER_ANIM_DURATION
    start()
}

Java

autoCenterAnimator = ObjectAnimator.ofInt(this, "Rotation", 0);
autoCenterAnimator.setIntValues(targetAngle);
autoCenterAnimator.setDuration(AUTOCENTER_ANIM_DURATION);
autoCenterAnimator.start();

Wenn der Wert, den Sie ändern möchten, eines der View-Basiseigenschaften ist, ist die Animation noch einfacher, da Ansichten eine integrierte ViewPropertyAnimator haben, die für die gleichzeitige Animation mehrerer Attribute optimiert ist, wie im folgenden Beispiel:

Kotlin

animate()
    .rotation(targetAngle)
    .duration = ANIM_DURATION
    .start()

Java

animate().rotation(targetAngle).setDuration(ANIM_DURATION).start();