Benutzerdefinierte Ansicht interaktiv gestalten

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

Das Zeichnen einer UI ist nur ein Teil des Erstellens einer benutzerdefinierten Ansicht. Außerdem muss Ihre Ansicht so auf Nutzereingaben reagieren, dass sie der realen Handlung ähnelt, die Sie imitieren.

Lassen Sie die Objekte in Ihrer App sich wie echte Objekte verhalten. Achten Sie beispielsweise darauf, dass Bilder in Ihrer App nicht auftauchen und an anderer Stelle wieder erscheinen, weil Objekte in der realen Welt dies nicht tun. Verschieben Sie Ihre Bilder stattdessen von einem Ort an einen anderen.

Nutzer spüren selbst subtiles Verhalten oder Gefühl in einer Benutzeroberfläche und reagieren am besten auf Feinheiten, die die reale Welt nachahmen. Wenn Nutzer beispielsweise ein UI-Objekt bewegen, vermitteln Sie ihnen ein Gefühl der Trägheit am Anfang, die die Bewegung verzögert. Vermitteln Sie ihnen am Ende der Bewegung ein Gefühl von Impuls, der das Objekt über den Ziehpunkt hinaus trägt.

Auf dieser Seite wird gezeigt, wie Sie Funktionen des Android-Frameworks verwenden können, um diese Verhaltensweisen aus der Praxis zu Ihrer benutzerdefinierten Ansicht hinzuzufügen.

Weitere zugehörige Informationen finden Sie unter Eingabeereignisse – Übersicht und Übersicht über die Property-Animation.

Eingabegesten verarbeiten

Wie viele andere UI-Frameworks unterstützt Android ein Eingabeereignismodell. Nutzeraktionen werden zu Ereignissen, die Callbacks auslösen. Sie können die 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 zur Verarbeitung des Ereignisses wie folgt:

Kotlin

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

Java

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

Touch-Ereignisse 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. Dazu übergeben Sie eine Instanz einer Klasse, die GestureDetector.OnGestureListener implementiert. Wenn Sie nur wenige Touch-Gesten verarbeiten möchten, können Sie GestureDetector.SimpleOnGestureListener erweitern, statt die GestureDetector.OnGestureListener-Schnittstelle 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, implementieren Sie immer eine onDown()-Methode, die true zurückgibt. Das ist erforderlich, da alle Touch-Gesten mit einer onDown()-Nachricht beginnen. Wenn Sie wie bei GestureDetector.SimpleOnGestureListener false von onDown() zurückgeben, geht das System davon aus, dass Sie den Rest der Touch-Geste ignorieren möchten. Die anderen Methoden von GestureDetector.OnGestureListener werden nicht aufgerufen. Geben Sie false nur dann von onDown() zurück, wenn Sie eine ganze Geste ignorieren möchten.

Nachdem Sie GestureDetector.OnGestureListener implementiert und eine Instanz von GestureDetector erstellt haben, können Sie mit GestureDetector die Touch-Ereignisse interpretieren, die Sie in onTouchEvent() empfangen.

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 Sie ein Touch-Ereignis an onTouchEvent() übergeben, das nicht als Teil einer Geste erkannt wird, wird false zurückgegeben. Sie können dann Ihren eigenen benutzerdefinierten Code zur Gestenerkennung ausführen.

Physikalische Bewegung erstellen

Gesten sind eine wirkungsvolle Möglichkeit, Geräte mit Touchscreen zu steuern. Sie sind jedoch oft konstruktiv und schwer zu merken, es sei denn, sie liefern physisch plausible Ergebnisse.

Angenommen, Sie möchten eine horizontale Ziehbewegung implementieren, bei der sich das in der Ansicht gezeichnete Element um seine vertikale Achse dreht. Diese Geste ist sinnvoll, wenn die Benutzeroberfläche als Reaktion darauf reagiert, indem sie sich schnell in die Richtung des Schleuders bewegt und dann langsamer wird, als ob der Nutzer auf ein Flywheel drückt und es dreht.

In der Dokumentation zum Animieren einer Scrollgeste findest du eine ausführliche Erklärung dazu, wie du dein eigenes Scoll-Verhalten implementieren kannst. Es ist jedoch nicht einfach, das Gefühl eines Schwungrads zu simulieren. Damit ein Schwungradmodell richtig funktioniert, ist viel Physik und Mathematik erforderlich. Glücklicherweise bietet Android Hilfsklassen, mit denen dieses und andere Verhaltensweisen simuliert werden können. Die Klasse Scroller ist die Grundlage für den Umgang mit Flywheel-Gesten.

Um einen Schlingvorgang zu starten, rufen Sie fling() mit der Startgeschwindigkeit und den minimalen und maximalen x- und y-Werten des Schleuders auf. Als 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;
}

Mit dem Aufruf von fling() wird das physikalische Modell für die fling-Geste eingerichtet. Aktualisieren Sie anschließend Scroller, indem Sie in regelmäßigen Abständen Scroller.computeScrollOffset() aufrufen. computeScrollOffset() aktualisiert den internen Zustand des Scroller-Objekts. Dazu wird die aktuelle Zeit gelesen und das physikalische Modell 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. In diesem Beispiel wird der Drehwinkel der Ansicht anhand der aktuellen x-Scrollposition festgelegt.

Kotlin

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

Java

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

Die Klasse Scroller berechnet die Scrollpositionen für Sie. Diese werden jedoch nicht automatisch auf Ihre Ansicht angewendet. Wenden Sie die neuen Koordinaten so oft an, dass die Scroll-Animation reibungslos wirkt. Dafür gibt es zwei Möglichkeiten:

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

Für einen reibungslosen Übergang

Nutzer erwarten, dass eine moderne Benutzeroberfläche reibungslos zwischen Status wechselt: UI-Elemente werden ein- und ausgeblendet, anstatt ein- und ausgeblendet zu werden, und Bewegungen, die reibungslos beginnen und enden, statt plötzlich zu starten und zu stoppen. Das Android-Framework für Property-Animationen erleichtert den Wechsel.

Wenn Sie das Animationssystem verwenden möchten, sollten Sie die Eigenschaft nicht direkt ändern, wenn sich eine Eigenschaft ändert, die sich auf die Darstellung der 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 Basisattribute vom Typ View ist, ist die Animation sogar noch einfacher, da Ansichten einen integrierten ViewPropertyAnimator haben, der für die gleichzeitige Animation mehrerer Eigenschaften optimiert ist, wie im folgenden Beispiel gezeigt:

Kotlin

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

Java

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