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. Das macht es schwieriger, Listenelemente zu sehen, die oben und unten auf dem Display angezeigt werden. Aus diesem Grund enthält Compose for Wear OS eine Version der Klasse LazyColumn namens TransformingLazyColumn, die Skalierungs- und Morphing-Animationen unterstützt.
Wenn Elemente an den Rand verschoben werden, werden sie kleiner und blenden aus.
So wenden Sie die empfohlenen Skalierungs- und Scrolling-Effekte an:
- Verwenden Sie
Modifier.transformedHeight, damit Compose die Höhenänderung berechnen kann, während das Element auf dem Bildschirm gescrollt wird. - Verwenden Sie
transformation = SurfaceTransformation(transformationSpec), um die visuellen Effekte anzuwenden, einschließlich des Herunterskalierens des Inhalts des Elements. - Verwenden Sie einen benutzerdefinierten
TransformationSpecfür Komponenten, dietransformationnicht als Parameter akzeptieren, z. B.Text.
Die folgende Animation zeigt, wie sich ein Listenelement skaliert und seine Form ändert, wenn es sich dem oberen und unteren Bildschirmrand nähert:
Das folgende Code-Snippet zeigt, wie Sie eine Liste mit dem LayoutTransformingLazyColumn erstellen, um Inhalte zu erstellen, die auf verschiedenen Wear OS-Displaygrößen gut aussehen.
Im Snippet wird auch die Verwendung des Modifikators minimumVerticalContentPadding veranschaulicht, den Sie für die Listenelemente festlegen sollten, um das richtige Padding oben und unten in der Liste anzuwenden.
Wenn Sie die Scrollmarkierung einblenden möchten, teilen Sie den columnState zwischen dem ScreenScaffold und dem TransformingLazyColumn auf:
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 nach einer Scroll- oder Wischbewegung so positioniert wird, dass ein Element genau an einer bestimmten Stelle, in der Regel in der Mitte des Bildschirms, angezeigt wird. Auf runden Displays, auf denen Elemente skaliert und transformiert werden, wenn sie sich vom Mittelpunkt entfernen, ist das Einrasten besonders nützlich, um sicherzustellen, dass das wichtigste Element im optimalen Sichtbereich 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.
Stelle die rotaryScrollableBehavior so ein, dass sie übereinstimmt. Verwende dazu RotaryScrollableDefaults.snapBehavior(columnState), um eine einheitliche Darstellung zu erzielen, wenn du die physische Krone oder Lünette verwendest.
val columnState = rememberTransformingLazyColumnState() ScreenScaffold(scrollState = columnState) { contentPadding -> TransformingLazyColumn( state = columnState, flingBehavior = TransformingLazyColumnDefaults.snapFlingBehavior(columnState), rotaryScrollableBehavior = RotaryScrollableDefaults.snapBehavior(columnState) ) { // ... // ... } }
Layout umkehren
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 erhalten. Wenn der Nutzer beispielsweise Element 10 unten auf dem Bildschirm sieht und Element 11 hinzugefügt wird, bleibt der Fokus auf Element 10 und Element 11 wird außerhalb des Bildschirms unter der aktuellen Ansicht angezeigt.
Für Anwendungsfälle wie Messaging-Apps oder Live-Logs ist dieses Verhalten in der Regel nicht erwünscht. Wenn neue Elemente eintreffen, möchten Nutzer in der Regel sofort die neuesten Inhalte sehen, wenn sie sich bereits am Ende der Liste befinden. Wenn viele Artikel gleichzeitig eintreffen, sollte in der Liste das neueste Element unten angezeigt werden. Das bedeutet, dass einige Elemente dazwischen möglicherweise gar nicht angezeigt werden, es sei denn, der Nutzer scrollt nach oben.
Um diese Anwendungsfälle zu unterstützen, können Sie mit TransformingLazyColumn das Layout umkehren, indem Sie reverseLayout = true festlegen. Dadurch wird der Anker der Liste von der oberen zur unteren Kante verschoben.
Wenn Sie reverseLayout = true festlegen, wird auch die visuelle Reihenfolge der Elemente und die Richtung der Scrollgesten umgekehrt:
- Die Elemente werden von unten nach oben zusammengesetzt. Das bedeutet, dass Index 0 unten auf dem Bildschirm angezeigt wird.
- Beim Scrollen nach oben werden Elemente mit höheren Indexen angezeigt.
Wenn Sie das Verhalten „Einrasten und schnippen“ zusammen mit dem umgekehrten Layout hinzufügen möchten, können Sie flingBehavior und rotaryScrollableBehavior kombinieren, wie im folgenden Snippet gezeigt:
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 Liste und einer umgekehrten Liste:
Edge-Schaltflächen in Listen
Bei Material 3 können Sie eine EdgeButton hinzufügen, einen Button, der sich am unteren Rand der Listen befindet. Achten Sie jedoch darauf, dies nicht als Element in TransformingLazyColumn hinzuzufügen, sondern den edgeButton-Slot in ScreenScaffold zu verwenden.
Durch die Verwendung des edgeButton-Slots wird dafür gesorgt, dass die Schaltfläche unten auf dem Bildschirm richtig positioniert ist und sich beim Scrollen der Liste richtig verhält.
Das folgende Code-Snippet zeigt, wie EdgeButton implementiert wird:
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, ) { // ... // ... } }
„SwipeToReveal“ in Listen
Mit der Komponente SwipeToReveal können Sie durch Wischen auf Aktionen für ein Listenelement zugreifen, z. B. Card oder Chip. Durch Wischen werden in der Regel ein oder zwei Aktionsschaltflächen (z. B. „Löschen“ oder „Mehr“) von der Seite eingeblendet.
Wenn Sie SwipeToReveal in einem TransformingLazyColumn verwenden, beachten Sie die folgenden Richtlinien:
- Beim Scrollen zurücksetzen: Wenn der Nutzer in der Liste scrollt, werden alle aufgeklappten Elemente in ihren ursprünglichen Zustand zurückgesetzt.
- Einheitliche Höhe: Die Höhe der Aktionsbuttons sollte der Höhe des inneren gewischten Elements (
ButtonoderCard) entsprechen, um ein einheitliches Erscheinungsbild zu erzielen. - Container transformieren: Wenden Sie den Modifier
transformedHeightundtransformationSpecauf die KomponenteSwipeToRevealan. - Nicht doppelt transformieren: Wenden Sie keine
transformedHeight- odertransformation-Modifizierer auf das innere gewischte Element (die Karte oder Schaltfläche imSwipeToReveal-Container) an.
Benutzerdefinierte Composables in Listen
Wenn Sie benutzerdefinierte Oberflächenkomponenten für ein TransformingLazyColumn erstellen, sollten Sie die folgenden Best Practices beachten, damit Ihre Composables in der Nähe der Displayränder reibungslos skaliert, ein- und ausgeblendet und ineinander überblendet werden:
SurfaceTransformationverfügbar machen: Akzeptieren Sie einen optionalenSurfaceTransformation-Parameter (Standardwert istnull), der mit Standard-Wear Compose Material 3-Komponenten wieCardundButtonübereinstimmt. So können Anrufer in einemTransformingLazyColumn-DurchgangSurfaceTransformation(transformationSpec), während die Komponente außerhalb einer Liste normal funktioniert.Modifier.transformedHeightzuerst im aufrufenden Code anwenden: Wenn Sie Ihre benutzerdefinierte Composable in einemTransformingLazyColumnplatzieren, übergeben SieModifier.transformedHeight(this, transformationSpec)als ersten Modifier in der Modifier-Kette des aufrufenden Codes. WährendSurfaceTransformationdie visuellen Skalierungs- und Einblendeffekte anwendet, isttransformedHeightentscheidend, um das Listenlayout anzuweisen, die Höhe des Elements neu zu berechnen, wenn es kleiner wird.- Transformationsschichten, Caller
modifierund Painter in der richtigen Reihenfolge anwenden:- Container-Transformationsschicht: Starten Sie die Modifikatorkette des Stammcontainers mit
Modifier.graphicsLayerundapplyContainerTransformation(), damit sowohl der Hintergrund als auch der Inhalt im skalierten, geneigten Koordinatensystem gezeichnet werden. - Aufrufer
modifier: Wenden Sie den vom Aufrufer übergebenen Parametermodifier(einschließlichModifier.transformedHeight) als Nächstes an, bevor Sie interne Größenanpassungen oder Abstände vornehmen. - Form zuschneiden, wenn keine Transformation vorhanden ist: Wenn
transformationgleichnullist, wenden SieModifier.clip(shape)vor dem Zeichnen des Hintergrunds an. Der voncreateContainerPainter()zurückgegebene Painter wird auf die Form zugeschnitten, ein einfacher Painter jedoch nicht. Ohne diese Zeile wird der Hintergrund außerhalb einer Liste mit quadratischen Ecken gezeichnet. - Morphing background painter: Zeichnen Sie den Hintergrund in der Container-Ebene mit
Modifier.drawBehindund einem Painter, der auscreateContainerPainter()erstellt wurde. - Ebene für die Inhaltsumwandlung: Wenden Sie einen zweiten
Modifier.graphicsLayermitapplyContentTransformation()und „clip“ auf die Containerform an, damit der innere Inhalt früher ausgeblendet wird, wenn er sich dem Rahmen nähert.
- Container-Transformationsschicht: Starten Sie die Modifikatorkette des Stammcontainers mit
Das folgende Snippet zeigt, wie eine benutzerdefinierte zusammensetzbare Funktion BoardingPassCard implementiert wird, die diese Transformationen in der richtigen Reihenfolge anwendet:
@Composable fun BoardingPassCard( flightNumber: String, origin: String, destination: String, gate: String, seat: String, departureTime: String, modifier: Modifier = Modifier, transformation: SurfaceTransformation? = null, shape: Shape = RoundedCornerShape(18.dp), statusBadge: @Composable () -> Unit = {} ) { // 1. Create morphing container painter val backgroundPainter = ColorPainter(MaterialTheme.colorScheme.surfaceContainer) val finalPainter = if (transformation != null) { remember(transformation, backgroundPainter, shape) { transformation.createContainerPainter(backgroundPainter, shape, border = null) } } else { backgroundPainter } Column( modifier = Modifier // 2a. Container layer: Scales, fades, and tilts the whole card surface .then( if (transformation != null) { Modifier.graphicsLayer { transformation.run { applyContainerTransformation() } } } else Modifier ) // 2b. Caller modifier: Includes Modifier.transformedHeight in a list .then(modifier) .fillMaxWidth() // 2c. Shape clip: Only needed without a transformation, because the // painter from createContainerPainter clips itself to the shape .then(if (transformation == null) Modifier.clip(shape) else Modifier) // 2d. Morphing background: Drawn inside the transformed container layer .drawBehind { with(finalPainter) { draw(size) } } // 2e. Content layer: Fades content earlier and clips children to shape .then( if (transformation != null) { Modifier.graphicsLayer { this.shape = shape this.clip = true transformation.run { applyContentTransformation() } } } else Modifier ) .padding(horizontal = 14.dp, vertical = 10.dp) ) { // Card content goes here } }
Sie können BoardingPassCard dann in einem TransformingLazyColumn verwenden, indem Sie Modifier.transformedHeight als ersten Modifikator zusammen mit SurfaceTransformation(transformationSpec) übergeben:
@Composable fun BoardingPassListSample(flights: List<FlightInfo>) { val listState = rememberTransformingLazyColumnState() val transformationSpec = rememberTransformationSpec() ScreenScaffold(scrollState = listState) { contentPadding -> TransformingLazyColumn( state = listState, contentPadding = contentPadding, modifier = Modifier.fillMaxSize() ) { items(flights.size) { index -> val flight = flights[index] BoardingPassCard( flightNumber = flight.number, origin = flight.origin, destination = flight.destination, gate = flight.gate, seat = flight.seat, departureTime = flight.time, modifier = Modifier .transformedHeight(this, transformationSpec) .minimumVerticalContentPadding( CardDefaults.minimumVerticalListContentPadding ), transformation = SurfaceTransformation(transformationSpec) ) } } } }
Empfehlungen für Sie
- Hinweis: Linktext wird angezeigt, wenn JavaScript deaktiviert ist
- Codelab: Compose für Wear OS
- Listen und Raster
- Ansichten in Compose verwenden