Listen mit Compose für Wear OS


Mit Listen können Nutzer auf Wear OS-Geräten ein Element aus einer Reihe von Optionen auswählen.

Viele Wear OS-Geräte haben runde Displays, wodurch es schwieriger ist, Listenelemente zu sehen, die sich oben und unten auf dem Display befinden. Aus diesem Grund, Compose für Wear OS enthält eine Version der Klasse LazyColumn namens TransformingLazyColumn, die Skalierungs- und Morphing-Animationen unterstützt. Wenn Elemente an die Ränder verschoben werden, werden sie kleiner und blenden aus.

So wenden Sie die empfohlenen Skalierungs- und Scrolling-Effekte an:

  1. Verwenden Sie Modifier.transformedHeight, damit Compose die Höhenänderung berechnen kann, während das Element durch den Bildschirm scrollt.
  2. Verwenden Sie transformation = SurfaceTransformation(transformationSpec), um die visuellen Effekte anzuwenden, einschließlich der Verkleinerung der Elementinhalte.
  3. Verwenden Sie eine benutzerdefinierte TransformationSpec für Komponenten, die transformation nicht als Parameter verwenden, z. B. Text.

Die folgende Animation zeigt, wie ein Listenelement skaliert und seine Form ändert, wenn es sich dem oberen und unteren Rand des Bildschirms nähert:

Das folgende Code-Snippet zeigt, wie Sie mit dem TransformingLazyColumn Layout eine Liste erstellen, deren Inhalte auf verschiedenen Wear OS-Displaygrößen gut aussehen.

Das Snippet zeigt auch die Verwendung des Modifikators minimumVerticalContentPadding, den Sie für die Listenelemente festlegen sollten, um den richtigen Abstand oben und unten in der Liste anzuwenden.

Wenn Sie die Scrollanzeige sehen möchten, geben Sie columnState zwischen ScreenScaffold und TransformingLazyColumn an:

val columnState = rememberTransformingLazyColumnState()
val transformationSpec = rememberTransformationSpec()
ScreenScaffold(
    scrollState = columnState
) { contentPadding ->
    TransformingLazyColumn(
        state = columnState,
        contentPadding = contentPadding
    ) {
        item {
            ListHeader(
                modifier = Modifier
                    .fillMaxWidth()
                    .transformedHeight(this, transformationSpec)
                    .minimumVerticalContentPadding(ListHeaderDefaults.minimumTopListContentPadding),
                transformation = SurfaceTransformation(transformationSpec)
            ) {
                Text(text = "Header")
            }
        }
        // ... other items
        item {
            Button(
                modifier = Modifier
                    .fillMaxWidth()
                    .transformedHeight(this, transformationSpec)
                    .minimumVerticalContentPadding(ButtonDefaults.minimumVerticalListContentPadding),
                transformation = SurfaceTransformation(transformationSpec),
                onClick = { /* ... */ },
                icon = {
                    Icon(
                        imageVector = Icons.Default.Build,
                        contentDescription = "build",
                    )
                },
            ) {
                Text(
                    text = "Build",
                    maxLines = 1,
                    overflow = TextOverflow.Ellipsis,
                )
            }
        }
    }
}

Snap-and-Fling-Effekt hinzufügen

Durch das Einrasten wird sichergestellt, dass die Liste, wenn ein Nutzer eine Scroll- oder zügig wischen-Geste beendet, mit einem Element an einer bestimmten Stelle, in der Regel in der Mitte des Bildschirms, anhält. Auf runden Displays, auf denen Elemente skaliert und morphing-animiert werden, wenn sie sich von der Mitte entfernen, ist das Einrasten besonders nützlich, um sicherzustellen, dass das relevanteste Element im optimalen Betrachtungsbereich vollständig sichtbar und lesbar bleibt.

Wenn Sie ein Snap-and-Fling-Verhalten hinzufügen möchten, legen Sie den Parameter flingBehavior auf TransformingLazyColumnDefaults.snapFlingBehavior(columnState) fest. Legen Sie rotaryScrollableBehavior entsprechend fest, indem Sie RotaryScrollableDefaults.snapBehavior(columnState) verwenden, um eine einheitliche Nutzererfahrung bei der Verwendung der physischen Krone oder Lünette zu gewährleisten.

val columnState = rememberTransformingLazyColumnState()
ScreenScaffold(scrollState = columnState) { contentPadding ->
    TransformingLazyColumn(
        state = columnState,
        flingBehavior = TransformingLazyColumnDefaults.snapFlingBehavior(columnState),
        rotaryScrollableBehavior = RotaryScrollableDefaults.snapBehavior(columnState)
    ) {
        // ...
        // ...
    }
}

Umgekehrtes Layout

Standardmäßig wird eine scrollbare Liste am oberen Rand verankert. Wenn ein Nutzer bis zum Ende einer Standardliste gescrollt hat und ein neues Element hinzugefügt wird, bleibt die Ansicht des Nutzers auf dem aktuellen Element. Wenn der Nutzer beispielsweise Element 10 unten auf dem Bildschirm sieht und Element 11 hinzugefügt wird, bleibt die Ansicht auf Element 10 fokussiert und Element 11 wird unter der aktuellen Ansicht außerhalb des Bildschirms angezeigt.

Für Anwendungsfälle wie Messaging-Apps oder Live-Logs ist dieses Verhalten in der Regel nicht erwünscht. Wenn neue Elemente eingehen, möchten Nutzer in der Regel die neuesten Inhalte sofort sehen, wenn sie sich bereits am Ende der Liste befinden. Wenn viele Elemente gleichzeitig eingehen, sollte die Liste zum neuesten Element unten springen. Das bedeutet, dass einige Elemente dazwischen möglicherweise gar nicht angezeigt werden, es sei denn, der Nutzer scrollt wieder nach oben.

Zur Unterstützung dieser Anwendungsfälle können Sie mit TransformingLazyColumn das Layout umkehren, indem Sie reverseLayout = true festlegen. Dadurch ändert sich der Anker der Liste vom oberen zum unteren Rand.

Wenn Sie reverseLayout = true festlegen, werden auch die visuelle Reihenfolge der Elemente und die Richtung der Scrollgesten umgekehrt:

  • Elemente werden von unten nach oben zusammengesetzt. Das bedeutet, dass Index 0 unten auf dem Bildschirm angezeigt wird.
  • Wenn Sie nach oben scrollen, werden Elemente mit höheren Indizes angezeigt.

Wenn Sie ein Snap-and-Fling-Verhalten zusammen mit einem umgekehrten Layout hinzufügen möchten, können Sie flingBehavior und rotaryScrollableBehavior wie im folgenden Snippet kombinieren:

val columnState = rememberTransformingLazyColumnState()
val transformationSpec = rememberTransformationSpec()
ScreenScaffold(scrollState = columnState) { contentPadding ->
    TransformingLazyColumn(
        state = columnState,
        contentPadding = contentPadding,
        reverseLayout = true,
        modifier = Modifier.fillMaxWidth()
    ) {
        items(10) { index ->
            Button(
                label = {
                    Text(
                        text = "Item ${index + 1}"
                    )
                },
                onClick = {},
                modifier = Modifier
                    .fillMaxWidth()
                    .transformedHeight(this, transformationSpec)
                    .minimumVerticalContentPadding(ButtonDefaults.minimumVerticalListContentPadding),
                transformation = SurfaceTransformation(transformationSpec)
            )
        }
        item {
            // With reverseLayout = true, the last item declared appears at the top.
            ListHeader(
                modifier = Modifier
                    .fillMaxWidth()
                    .transformedHeight(this, transformationSpec)
                    .minimumVerticalContentPadding(ListHeaderDefaults.minimumTopListContentPadding),
                transformation = SurfaceTransformation(transformationSpec)
            ) {
                Text("Header")
            }
        }
    }
}

Die folgenden Bilder zeigen den Unterschied zwischen einer normalen und einer umgekehrten Liste:

Eine TransformingLazyColumn mit normalem Layout, in der „Item 1“ oben und die Elemente in aufsteigender Reihenfolge angezeigt werden.
Abbildung 1. Ein Standardlistenlayout, bei dem Inhalte von oben nach unten eingefügt werden.
Eine TransformingLazyColumn mit umgekehrter Anordnung, in der „Item 1“ unten und die Elemente in absteigender Reihenfolge nach oben angezeigt werden.
Abbildung 2. Ein umgekehrtes Listenlayout, bei dem Inhalte von unten nach oben eingefügt werden.

Randbuttons in Listen

Für Material 3 können Sie einen EdgeButton hinzufügen, einen Button, der am unteren Rand der Listen angebracht ist. Fügen Sie diesen jedoch nicht als Element in TransformingLazyColumn ein, sondern verwenden Sie stattdessen den Slot edgeButton in ScreenScaffold.

Durch die Verwendung des Slots edgeButton wird sichergestellt, dass der Button korrekt am unteren Rand des Bildschirms positioniert ist und sich beim Scrollen der Liste richtig verhält.

Das folgende Code-Snippet zeigt, wie Sie EdgeButton implementieren:

val columnState = rememberTransformingLazyColumnState()
ScreenScaffold(
    scrollState = columnState,
    edgeButton = {
        EdgeButton(
            onClick = { /* TODO */ },
            modifier = Modifier.scrollable(
                columnState,
                orientation = Orientation.Vertical,
                reverseDirection = true,
                // Apply overscroll to the EdgeButton for proper scrolling behavior.
                overscrollEffect = rememberOverscrollEffect(),
            )
        ) {
            Text("More")
        }
    }
) { contentPadding ->
    TransformingLazyColumn(
        contentPadding = contentPadding,
        state = columnState,
    ) {
        // ...
        // ...
    }
}

„Zum Aufdecken wischen“ in Listen

Mit der Komponente SwipeToReveal können Sie durch Wischen auf Aktionen für ein Listenelement zugreifen, z. B. eine Card oder Chip. Durch Wischen werden in der Regel ein oder zwei Aktionsbuttons (z. B. „Löschen“ oder „Mehr“) von der Seite angezeigt.

Wenn Sie SwipeToReveal in einer TransformingLazyColumn verwenden, beachten Sie die folgenden Richtlinien:

  • Beim Scrollen zurücksetzen: Wenn der Nutzer in der Liste scrollt, setzen Sie alle aufgewischten Elemente in den verdeckten Zustand zurück.
  • Einheitliche Höhen: Legen Sie die Höhen der Aktionsbuttons so fest, dass sie mit dem inneren aufgewischten Element übereinstimmen (unabhängig davon, ob es sich um ein Button oder eine Card handelt), um ein einheitliches Erscheinungsbild zu gewährleisten.
  • Container transformieren: Wenden Sie den Modifikator transformedHeight und transformationSpec auf die Komponente SwipeToReveal selbst an.
  • Nicht doppelt transformieren: Wenden Sie die Modifikatoren transformedHeight oder transformation nicht auf das innere aufgewischte Element an (die Karte oder den Button im Container SwipeToReveal).