Wischen, um die Benachrichtigung zu schließen oder zu aktualisieren

Mit der Komponente SwipeToDismissBox können Nutzer ein Element schließen oder aktualisieren, indem sie es nach links oder rechts wischen.

API-Oberfläche

Verwenden Sie das SwipeToDismissBox-Element, um Aktionen zu implementieren, die durch Wischbewegungen ausgelöst werden. Zu den wichtigsten Parametern gehören:

  • state: Der SwipeToDismissBoxState-Status, der zum Speichern des durch Berechnungen am Wischelement generierten Werts erstellt wird, wodurch Ereignisse ausgelöst werden.
  • backgroundContent: Ein anpassbares Element, das hinter dem Artikelinhalt angezeigt wird und sichtbar wird, wenn der Inhalt wischt.

Einfaches Beispiel: Aktualisieren oder schließen durch Wischen

Die Snippets in diesem Beispiel zeigen eine Wischimplementierung, bei der das Element entweder aktualisiert wird, wenn von Anfang nach Ende gewischt wird, oder geschlossen wird, wenn von Ende nach Anfang gewischt wird.

data class TodoItem(
    val itemDescription: String,
    var isItemDone: Boolean = false
)

@Composable
fun TodoListItem(
    todoItem: TodoItem,
    onToggleDone: (TodoItem) -> Unit,
    onRemove: (TodoItem) -> Unit,
    modifier: Modifier = Modifier,
) {
    val swipeToDismissBoxState = rememberSwipeToDismissBoxState(
        confirmValueChange = {
            if (it == StartToEnd) onToggleDone(todoItem)
            else if (it == EndToStart) onRemove(todoItem)
            // Reset item when toggling done status
            it != StartToEnd
        }
    )

    SwipeToDismissBox(
        state = swipeToDismissBoxState,
        modifier = modifier.fillMaxSize(),
        backgroundContent = {
            when (swipeToDismissBoxState.dismissDirection) {
                StartToEnd -> {
                    Icon(
                        if (todoItem.isItemDone) Icons.Default.CheckBox else Icons.Default.CheckBoxOutlineBlank,
                        contentDescription = if (todoItem.isItemDone) "Done" else "Not done",
                        modifier = Modifier
                            .fillMaxSize()
                            .background(Color.Blue)
                            .wrapContentSize(Alignment.CenterStart)
                            .padding(12.dp),
                        tint = Color.White
                    )
                }
                EndToStart -> {
                    Icon(
                        imageVector = Icons.Default.Delete,
                        contentDescription = "Remove item",
                        modifier = Modifier
                            .fillMaxSize()
                            .background(Color.Red)
                            .wrapContentSize(Alignment.CenterEnd)
                            .padding(12.dp),
                        tint = Color.White
                    )
                }
                Settled -> {}
            }
        }
    ) {
        ListItem(
            headlineContent = { Text(todoItem.itemDescription) },
            supportingContent = { Text("swipe me to update or remove.") }
        )
    }
}

Wichtige Punkte zum Code

  • swipeToDismissBoxState verwaltet den Komponentenstatus. Er löst den confirmValueChange-Callback aus, sobald die Interaktion mit dem Artikel abgeschlossen ist. Im Callback-Body werden die verschiedenen möglichen Aktionen verarbeitet. Der Callback gibt einen booleschen Wert zurück, der der Komponente mitteilt, ob eine Schließanimation angezeigt werden soll. In diesem Fall:
    • Wenn das Element von Anfang bis Ende wischt, wird das onToggleDone-Lambda aufgerufen und die aktuelle todoItem übergeben. Das entspricht dem Aktualisieren des To-do-Eintrags.
    • Wenn das Element vom Ende zum Anfang wischt, wird das Lambda-Objekt onRemove aufgerufen und die aktuelle todoItem übergeben. Das entspricht dem Löschen des To-do-Eintrags.
    • it != StartToEnd: Diese Zeile gibt true zurück, wenn die Wischrichtung nicht StartToEnd ist, andernfalls false. Wenn Sie false zurückgeben, verschwindet SwipeToDismissBox nicht sofort nach dem Wischen zum Ausschalten. So ist eine visuelle Bestätigung oder Animation möglich.
  • Mit SwipeToDismissBox können Sie bei jedem Element horizontal wischen. Im Ruhemodus wird der innere Inhalt der Komponente angezeigt. Wenn ein Nutzer jedoch wischt, wird der Inhalt ausgeblendet und das backgroundContent wird angezeigt. Sowohl die normalen Inhalte als auch das backgroundContent erhalten die vollständigen Einschränkungen des übergeordneten Containers, in dem sie gerendert werden. Das content wird über dem backgroundContent gezeichnet. In diesem Fall:
    • backgroundContent wird als Icon mit einer Hintergrundfarbe implementiert, die auf SwipeToDismissBoxValue basiert:
    • Blue beim Wischen auf StartToEnd: Ein-/Ausschalten eines To-do-Eintrags.
    • Red beim Wischen auf EndToStart: Löschen eines To-do-Eintrags.
    • Für Settled wird im Hintergrund nichts angezeigt. Wenn das Element nicht gewischt wird, wird im Hintergrund nichts angezeigt.
    • Ebenso passt sich das angezeigte Icon an die Wischrichtung an:
    • Bei StartToEnd wird das Symbol CheckBox angezeigt, wenn die Aufgabe erledigt ist, und das Symbol CheckBoxOutlineBlank, wenn sie noch nicht erledigt ist.
    • EndToStart wird ein Delete-Symbol angezeigt.

@Composable
private fun SwipeItemExample() {
    val todoItems = remember {
        mutableStateListOf(
            TodoItem("Pay bills"), TodoItem("Buy groceries"),
            TodoItem("Go to gym"), TodoItem("Get dinner")
        )
    }

    LazyColumn {
        items(
            items = todoItems,
            key = { it.itemDescription }
        ) { todoItem ->
            TodoListItem(
                todoItem = todoItem,
                onToggleDone = { todoItem ->
                    todoItem.isItemDone = !todoItem.isItemDone
                },
                onRemove = { todoItem ->
                    todoItems -= todoItem
                },
                modifier = Modifier.animateItem()
            )
        }
    }
}

Wichtige Punkte zum Code

  • mutableStateListOf(...) erstellt eine Observable-Liste, die TodoItem-Objekte enthalten kann. Wenn ein Element dieser Liste hinzugefügt oder daraus entfernt wird, werden die entsprechenden Teile der Benutzeroberfläche in Compose neu zusammengesetzt.
    • Innerhalb von mutableStateListOf() werden vier TodoItem-Objekte mit ihren jeweiligen Beschreibungen initialisiert: „Rechnungen bezahlen“, „Einkäufe erledigen“, „In die Turnhalle gehen“ und „Abendessen holen“.
  • Mit LazyColumn wird eine vertikal scrollbare Liste von todoItems angezeigt.
  • onToggleDone = { todoItem -> ... } ist eine Callback-Funktion, die von TodoListItem aufgerufen wird, wenn der Nutzer ein Objekt als erledigt markiert. Dadurch wird das Attribut isItemDone des todoItem aktualisiert. Da todoItems ein mutableStateListOf ist, löst diese Änderung eine Neuzusammensetzung aus, wodurch die Benutzeroberfläche aktualisiert wird.
  • onRemove = { todoItem -> ... } ist eine Callback-Funktion, die ausgelöst wird, wenn der Nutzer das Element entfernt. Dadurch wird die spezifische todoItem aus der Liste der todoItems entfernt. Dadurch wird auch die Zusammensetzung neu berechnet und das Element wird aus der angezeigten Liste entfernt.
  • Auf jede TodoListItem wird ein animateItem-Modifikator angewendet, damit die placementSpec des Modifikators aufgerufen wird, wenn das Element geschlossen wurde. Dadurch wird das Entfernen des Artikels sowie die Neuanordnung anderer Artikel in der Liste animiert.

Ergebnis

Im folgenden Video wird die grundlegende Funktion „Wischen zum Schließen“ aus den vorherigen Snippets veranschaulicht:

Abbildung 1. Eine grundlegende Implementierung von „Wischen zum Schließen“, mit der ein Element sowohl als erledigt markiert als auch eine Schließanimation für ein Element in einer Liste angezeigt werden kann.

Den vollständigen Code finden Sie in der GitHub-Quelldatei.

Fortgeschrittenes Beispiel: Hintergrundfarbe beim Wischen animieren

In den folgenden Snippets wird gezeigt, wie Sie einen Positionsgrenzwert verwenden, um die Hintergrundfarbe eines Elements beim Wischen zu animieren.

data class TodoItem(
    val itemDescription: String,
    var isItemDone: Boolean = false
)

@Composable
fun TodoListItemWithAnimation(
    todoItem: TodoItem,
    onToggleDone: (TodoItem) -> Unit,
    onRemove: (TodoItem) -> Unit,
    modifier: Modifier = Modifier,
) {
    val swipeToDismissBoxState = rememberSwipeToDismissBoxState(
        confirmValueChange = {
            if (it == StartToEnd) onToggleDone(todoItem)
            else if (it == EndToStart) onRemove(todoItem)
            // Reset item when toggling done status
            it != StartToEnd
        }
    )

    SwipeToDismissBox(
        state = swipeToDismissBoxState,
        modifier = modifier.fillMaxSize(),
        backgroundContent = {
            when (swipeToDismissBoxState.dismissDirection) {
                StartToEnd -> {
                    Icon(
                        if (todoItem.isItemDone) Icons.Default.CheckBox else Icons.Default.CheckBoxOutlineBlank,
                        contentDescription = if (todoItem.isItemDone) "Done" else "Not done",
                        modifier = Modifier
                            .fillMaxSize()
                            .drawBehind {
                                drawRect(lerp(Color.LightGray, Color.Blue, swipeToDismissBoxState.progress))
                            }
                            .wrapContentSize(Alignment.CenterStart)
                            .padding(12.dp),
                        tint = Color.White
                    )
                }
                EndToStart -> {
                    Icon(
                        imageVector = Icons.Default.Delete,
                        contentDescription = "Remove item",
                        modifier = Modifier
                            .fillMaxSize()
                            .background(lerp(Color.LightGray, Color.Red, swipeToDismissBoxState.progress))
                            .wrapContentSize(Alignment.CenterEnd)
                            .padding(12.dp),
                        tint = Color.White
                    )
                }
                Settled -> {}
            }
        }
    ) {
        OutlinedCard(shape = RectangleShape) {
            ListItem(
                headlineContent = { Text(todoItem.itemDescription) },
                supportingContent = { Text("swipe me to update or remove.") }
            )
        }
    }
}

Wichtige Punkte zum Code

  • drawBehind wird direkt in den Canvas hinter dem Inhalt des Icon-Kompositionselements gezeichnet.
    • drawRect() zeichnet ein Rechteck auf dem Canvas und füllt den gesamten Zeichnungsbereich mit dem angegebenen Color aus.
  • Beim Wischen ändert sich die Hintergrundfarbe des Artikels mit lerp stufenlos.
    • Bei einem Wischen von StartToEnd ändert sich die Hintergrundfarbe nach und nach von Hellgrau zu Blau.
    • Bei einem Wischen von EndToStart ändert sich die Hintergrundfarbe nach und nach von Hellgrau zu Rot.
    • Der Übergang von einer Farbe zur nächsten wird durch swipeToDismissBoxState.progress bestimmt.
  • OutlinedCard sorgt für eine subtile visuelle Trennung zwischen den Listenelementen.

@Composable
private fun SwipeItemWithAnimationExample() {
    val todoItems = remember {
        mutableStateListOf(
            TodoItem("Pay bills"), TodoItem("Buy groceries"),
            TodoItem("Go to gym"), TodoItem("Get dinner")
        )
    }

    LazyColumn {
        items(
            items = todoItems,
            key = { it.itemDescription }
        ) { todoItem ->
            TodoListItemWithAnimation(
                todoItem = todoItem,
                onToggleDone = { todoItem ->
                    todoItem.isItemDone = !todoItem.isItemDone
                },
                onRemove = { todoItem ->
                    todoItems -= todoItem
                },
                modifier = Modifier.animateItem()
            )
        }
    }
}

Wichtige Punkte zum Code

  • Die wichtigsten Punkte zu diesem Code finden Sie unter Wichtige Punkte in einem vorherigen Abschnitt, in dem ein identisches Code-Snippet beschrieben wird.

Ergebnis

Im folgenden Video sind die erweiterten Funktionen mit animierter Hintergrundfarbe zu sehen:

Abbildung 2. Eine Implementierung des Wischens zum Anzeigen oder Löschen mit animierten Hintergrundfarben und einem längeren Grenzwert, bevor die Aktion registriert wird.

Den vollständigen Code finden Sie in der GitHub-Quelldatei.

Zusätzliche Ressourcen