Benutzerdefinierte Ansicht interaktiv gestalten

Jetpack Compose
Jetpack Compose ist das empfohlene UI-Toolkit für Android. Informationen zum Arbeiten mit Layouts in Compose

Das Zeichnen einer UI ist nur ein Teil der Erstellung einer benutzerdefinierten Ansicht. Sie müssen auch dafür sorgen, dass Ihre Ansicht auf Nutzereingaben so reagiert, dass sie der realen Aktion, die Sie nachahmen, möglichst nahe kommt.

Die Objekte in Ihrer App sollten sich wie echte Objekte verhalten. Bilder in Ihrer App sollten beispielsweise nicht einfach verschwinden und an anderer Stelle wieder auftauchen, da Objekte in der realen Welt das nicht tun. Stattdessen sollten Sie Ihre Bilder von einem Ort zum anderen bewegen.

Nutzer nehmen selbst subtile Verhaltensweisen oder Gefühle in einer Benutzeroberfläche wahr und reagieren am besten auf Feinheiten, die die reale Welt nachahmen. Wenn Nutzer beispielsweise ein UI-Objekt zügig wischen, sollten sie am Anfang ein Gefühl der Trägheit haben, das die Bewegung verzögert. Am Ende der Bewegung sollten sie ein Gefühl des Schwungs haben, das das Objekt über den Schleudervorgang hinaus bewegt.

Auf dieser Seite wird gezeigt, wie Sie mit Funktionen des Android-Frameworks diese Verhaltensweisen aus der realen Welt zu Ihrer benutzerdefinierten Ansicht hinzufügen.

Weitere Informationen finden Sie unter Übersicht über Eingabeereignisse und Übersicht über Attributanimationen.

Eingabegesten verarbeiten

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

Kotlin

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

Java

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

Touch-Ereignisse sind an sich nicht besonders nützlich. Moderne Touch-UIs definieren Interaktionen als Gesten wie Tippen, Ziehen, Schieben, Schleudern und Zoomen. Um Touch-Ereignisse in Gesten umzuwandeln, bietet Android GestureDetector.

Erstellen Sie ein GestureDetector-Objekt, indem Sie eine Instanz einer Klasse übergeben, die GestureDetector.OnGestureListener implementiert. Wenn Sie nur einige wenige Gesten verarbeiten möchten, können Sie stattdessen GestureDetector.SimpleOnGestureListener erweitern, anstatt die Schnittstelle GestureDetector.OnGestureListener 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, müssen Sie immer eine onDown()-Methode implementieren, die true zurückgibt. Das ist erforderlich, da alle Gesten mit einer onDown()-Nachricht beginnen. Wenn Sie von onDown() den Wert false zurückgeben, wie es bei GestureDetector.SimpleOnGestureListener der Fall ist, geht das System davon aus, dass Sie den Rest der Touchgeste ignorieren möchten, und die anderen Methoden von GestureDetector.OnGestureListener werden nicht aufgerufen. Geben Sie nur dann false von onDown() zurück, wenn Sie eine gesamte Geste ignorieren möchten.

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

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

Physikalisch plausible Bewegungen erstellen

Gesten sind eine leistungsstarke Möglichkeit, Touchscreen-Geräte zu steuern. Sie können jedoch kontraintuitiv und schwer zu merken sein, wenn sie keine physikalisch plausiblen Ergebnisse liefern.

Angenommen, Sie möchten eine horizontale Touchgeste zum zügigen Wischen implementieren, mit der sich das in der Ansicht gezeichnete Element um seine vertikale Achse dreht. Diese Touchgeste ist sinnvoll, wenn die UI schnell in Richtung des zügigen Wischens bewegt wird und dann langsamer wird, als würde der Nutzer ein Schwungrad anschieben und drehen.

In der Dokumentation zum Animieren einer Scrollgeste wird ausführlich erklärt, wie Sie Ihr eigenes Scrollverhalten implementieren. Das Gefühl eines Schwungrads zu simulieren ist jedoch nicht einfach. Es ist viel Physik und Mathematik erforderlich, damit ein Schwungradmodell richtig funktioniert. Glücklicherweise bietet Android Hilfsklassen, um dieses und andere Verhaltensweisen zu simulieren. Die Klasse Scroller ist die Grundlage für die Verarbeitung von Schleudergesten im Schwungradstil.

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

Mit dem Aufruf von fling() wird das physikalische Modell für die Schleudergeste eingerichtet. Aktualisieren Sie anschließend Scroller in regelmäßigen Abständen mit Scroller.computeScrollOffset(). computeScrollOffset() aktualisiert den internen Status des Scroller-Objekts, indem die aktuelle Zeit gelesen und mit dem physikalischen Modell die x - und y -Position zu diesem Zeitpunkt berechnet wird. Rufen Sie getCurrX() und getCurrY() auf, um diese Werte abzurufen.

Die meisten Ansichten übergeben die x - und y -Positionen des Scroller-Objekts direkt an scrollTo(). Dieses Beispiel ist etwas anders: Es verwendet die aktuelle x -Scrollposition, 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 aber nicht automatisch auf Ihre Ansicht an. Wenden Sie neue Koordinaten häufig genug an, damit die Scrollanimation reibungslos aussieht. Dazu gibt es zwei Möglichkeiten:

  • Erzwingen Sie ein erneutes Zeichnen, indem Sie postInvalidate() nach dem Aufruf von fling() aufrufen. Bei dieser Methode müssen Sie Scroll-Offsets in onDraw() berechnen und postInvalidate() jedes Mal aufrufen, wenn sich der Scroll-Offset ändert.
  • Richten Sie ein ValueAnimator ein, um die Animation für die Dauer des Schleudervorgangs auszuführen, und fügen Sie einen Listener hinzu, um Animationsaktualisierungen mit addUpdateListener() zu verarbeiten. Mit dieser Methode können Sie Attribute einer View animieren.

Reibungslose Übergänge

Nutzer erwarten, dass eine moderne UI reibungslos zwischen den Zuständen wechselt: UI-Elemente werden ein- und ausgeblendet, anstatt einfach zu erscheinen und zu verschwinden, und Bewegungen beginnen und enden reibungslos, anstatt abrupt zu starten und zu stoppen. Das Android Framework für Attributanimationen erleichtert reibungslose Übergänge.

Wenn Sie das Animationssystem verwenden möchten, ändern Sie das Attribut nicht direkt, wenn sich ein Attribut ändert, das sich auf das Erscheinungsbild Ihrer Ansicht auswirkt. Verwenden Sie stattdessen ValueAnimator, um die Änderung vorzunehmen. Im folgenden Beispiel wird durch Ändern der ausgewählten untergeordneten Komponente in der Ansicht die gesamte gerenderte Ansicht gedreht, sodass der Auswahlzeiger zentriert ist. ValueAnimator ändert die Drehung über einen Zeitraum von mehreren Hundert Millisekunden, anstatt den neuen Drehwert 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 Basis-View Attribute ist, ist die Animation noch einfacher, da Ansichten einen integrierten ViewPropertyAnimator haben, der 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();