Viele Apps müssen Sammlungen von Elementen anzeigen. In diesem Dokument wird erläutert, wie Sie dies in Jetpack Compose effizient tun können.
Wenn Sie wissen, dass in Ihrem Anwendungsfall kein Scrollen erforderlich ist, können Sie ein einfaches Column
oder Row
(je nach Richtung) verwenden und den Inhalt der einzelnen Elemente ausgeben, indem Sie auf folgende Weise eine Liste durchlaufen:
@Composable fun MessageList(messages: List<Message>) { Column { messages.forEach { message -> MessageRow(message) } } }
Mit dem Modifikator verticalScroll()
können wir Column
scrollbar machen.
Verzögerte Listen
Wenn Sie eine große Anzahl von Elementen (oder eine Liste mit unbekannter Länge) anzeigen müssen, kann die Verwendung eines Layouts wie Column
zu Leistungsproblemen führen, da alle Elemente unabhängig davon, ob sie sichtbar sind, zusammengesetzt und angeordnet werden.
Compose bietet eine Reihe von Komponenten, die nur Elemente zusammensetzen und anordnen, die im Darstellungsbereich der Komponente sichtbar sind. Zu diesen Komponenten gehören
LazyColumn
und
LazyRow
.
Wie der Name schon sagt, besteht der Unterschied zwischen
LazyColumn
und
LazyRow
in der Ausrichtung, in der die Elemente angeordnet und gescrollt werden. LazyColumn
erzeugt eine vertikal scrollende Liste und LazyRow
eine horizontal scrollende Liste.
Die Lazy-Komponenten unterscheiden sich von den meisten Layouts in Compose. Anstelle eines @Composable
-Parameters für Inhaltsblöcke, der es Apps ermöglicht, Composables direkt auszugeben, bieten die Lazy-Komponenten einen LazyListScope.()
-Block. Dieser LazyListScope
-Block bietet eine DSL, mit der Apps den Inhalt des Artikels beschreiben können. Die Lazy-Komponente ist dann dafür verantwortlich, die Inhalte der einzelnen Elemente nach Bedarf entsprechend dem Layout und der Scrollposition hinzuzufügen.
LazyListScope
DSL
Die DSL von LazyListScope
bietet eine Reihe von Funktionen zum Beschreiben von Elementen im Layout. Im einfachsten Fall fügt item()
ein einzelnes Element und items(Int)
mehrere Elemente hinzu:
LazyColumn { // Add a single item item { Text(text = "First item") } // Add 5 items items(5) { index -> Text(text = "Item: $index") } // Add another single item item { Text(text = "Last item") } }
Es gibt auch eine Reihe von Erweiterungsfunktionen, mit denen Sie Sammlungen von Elementen wie List
hinzufügen können. Mit diesen Erweiterungen können wir das obige Beispiel Column
ganz einfach migrieren:
/** * import androidx.compose.foundation.lazy.items */ LazyColumn { items(messages) { message -> MessageRow(message) } }
Es gibt auch eine Variante der Erweiterungsfunktion items()
namens itemsIndexed()
, die den Index bereitstellt. Weitere Informationen finden Sie in der Referenz zu LazyListScope
.
Lazy Grids
Die Composables
LazyVerticalGrid
und
LazyHorizontalGrid
> unterstützen die Anzeige von Elementen in einem Raster. In einem Lazy Vertical Grid werden die Elemente in einem vertikal scrollbaren Container angezeigt, der sich über mehrere Spalten erstreckt. Lazy Horizontal Grids verhalten sich auf der horizontalen Achse genauso.
Grids haben dieselben leistungsstarken API-Funktionen wie Listen und verwenden auch eine sehr ähnliche DSL – LazyGridScope.()
– zum Beschreiben des Inhalts.
Mit dem Parameter columns
in LazyVerticalGrid
und dem Parameter rows
in LazyHorizontalGrid
wird gesteuert, wie Zellen in Spalten oder Zeilen angeordnet werden. Im folgenden Beispiel werden Elemente in einem Raster dargestellt. Dabei wird GridCells.Adaptive
verwendet, um die Breite jeder Spalte auf mindestens 128.dp
festzulegen:
LazyVerticalGrid( columns = GridCells.Adaptive(minSize = 128.dp) ) { items(photos) { photo -> PhotoItem(photo) } }
Mit LazyVerticalGrid
können Sie eine Breite für Elemente angeben. Das Raster passt dann so viele Spalten wie möglich ein. Die verbleibende Breite wird gleichmäßig auf die Spalten verteilt, nachdem die Anzahl der Spalten berechnet wurde.
Diese adaptive Art der Größenanpassung ist besonders nützlich, um Gruppen von Elementen auf verschiedenen Bildschirmgrößen darzustellen.
Wenn Sie die genaue Anzahl der zu verwendenden Spalten kennen, können Sie stattdessen eine Instanz von GridCells.Fixed
mit der Anzahl der erforderlichen Spalten angeben.
Wenn Ihr Design nur für bestimmte Elemente nicht standardmäßige Abmessungen erfordert, können Sie das Raster verwenden, um benutzerdefinierte Spannen für Elemente anzugeben.
Geben Sie die Spaltenbreite mit dem Parameter span
der Methoden LazyGridScope DSL
item
und items
an.
maxLineSpan
, einer der Werte des Bereichs, ist besonders nützlich, wenn Sie die adaptive Größenanpassung verwenden, da die Anzahl der Spalten nicht festgelegt ist.
In diesem Beispiel wird gezeigt, wie ein vollständiger Zeilenbereich angegeben wird:
LazyVerticalGrid( columns = GridCells.Adaptive(minSize = 30.dp) ) { item(span = { // LazyGridItemSpanScope: // maxLineSpan GridItemSpan(maxLineSpan) }) { CategoryCard("Fruits") } // ... }
Lazy-Staggered-Raster
LazyVerticalStaggeredGrid
und LazyHorizontalStaggeredGrid
sind Composables, mit denen Sie ein verzögert geladenes, gestaffeltes Raster von Elementen erstellen können.
In einem vertikalen, gestaffelten Lazy-Grid werden die Elemente in einem vertikal scrollbaren Container angezeigt, der sich über mehrere Spalten erstreckt und in dem die einzelnen Elemente unterschiedliche Höhen haben können. Lazy Horizontal Grids verhalten sich auf der horizontalen Achse bei Elementen mit unterschiedlichen Breiten gleich.
Das folgende Snippet ist ein einfaches Beispiel für die Verwendung von LazyVerticalStaggeredGrid
mit einer 200.dp
-Breite pro Element:
LazyVerticalStaggeredGrid( columns = StaggeredGridCells.Adaptive(200.dp), verticalItemSpacing = 4.dp, horizontalArrangement = Arrangement.spacedBy(4.dp), content = { items(randomSizedPhotos) { photo -> AsyncImage( model = photo, contentScale = ContentScale.Crop, contentDescription = null, modifier = Modifier .fillMaxWidth() .wrapContentHeight() ) } }, modifier = Modifier.fillMaxSize() )
Wenn Sie eine feste Anzahl von Spalten festlegen möchten, können Sie anstelle von StaggeredGridCells.Adaptive
auch StaggeredGridCells.Fixed(columns)
verwenden.
Dabei wird die verfügbare Breite durch die Anzahl der Spalten (oder Zeilen für ein horizontales Raster) geteilt und jedes Element nimmt diese Breite (oder Höhe für ein horizontales Raster) ein:
LazyVerticalStaggeredGrid( columns = StaggeredGridCells.Fixed(3), verticalItemSpacing = 4.dp, horizontalArrangement = Arrangement.spacedBy(4.dp), content = { items(randomSizedPhotos) { photo -> AsyncImage( model = photo, contentScale = ContentScale.Crop, contentDescription = null, modifier = Modifier .fillMaxWidth() .wrapContentHeight() ) } }, modifier = Modifier.fillMaxSize() )

Innenabstand von Inhalten
Manchmal müssen Sie um die Ränder der Inhalte herum Innenabstand hinzufügen. Mit den Lazy-Komponenten können Sie einige PaddingValues
an den Parameter contentPadding
übergeben, um dies zu unterstützen:
LazyColumn( contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp), ) { // ... }
In diesem Beispiel fügen wir den horizontalen Rändern (links und rechts) einen Innenabstand von 16.dp
und dem oberen und unteren Rand des Inhalts einen Innenabstand von 8.dp
hinzu.
Diese Auffüllung wird auf den Inhalt und nicht auf das LazyColumn
selbst angewendet. Im obigen Beispiel wird dem ersten Element 8.dp
-Abstand oben, dem letzten Element 8.dp
-Abstand unten und allen Elementen 16.dp
-Abstand links und rechts hinzugefügt.
Ein weiteres Beispiel: Sie können die PaddingValues
von Scaffold
in die contentPadding
von LazyColumn
übergeben. Weitere Informationen finden Sie im Leitfaden für die Darstellung von Inhalten von Rand zu Rand.
Abstand zwischen Inhalten
Wenn Sie zwischen Elementen Abstände einfügen möchten, können Sie Arrangement.spacedBy()
verwenden.
Im folgenden Beispiel wird zwischen den einzelnen Elementen ein Abstand von 4.dp
eingefügt:
LazyColumn( verticalArrangement = Arrangement.spacedBy(4.dp), ) { // ... }
Ähnlich für LazyRow
:
LazyRow( horizontalArrangement = Arrangement.spacedBy(4.dp), ) { // ... }
Bei Rastern sind jedoch sowohl vertikale als auch horizontale Anordnungen möglich:
LazyVerticalGrid( columns = GridCells.Fixed(2), verticalArrangement = Arrangement.spacedBy(16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp) ) { items(photos) { item -> PhotoItem(item) } }
Schlüssel des Feedelements
Standardmäßig wird der Status jedes Elements anhand seiner Position in der Liste oder im Raster festgelegt. Dies kann jedoch zu Problemen führen, wenn sich das Dataset ändert, da Elemente, die ihre Position ändern, effektiv ihren gespeicherten Status verlieren. Stellen Sie sich das Szenario von LazyRow
in einem LazyColumn
vor. Wenn sich die Position des Elements in der Zeile ändert, verliert der Nutzer seine Scrollposition in der Zeile.
Um dem entgegenzuwirken, können Sie für jedes Element einen stabilen und eindeutigen Schlüssel angeben, der einen Block für den key
-Parameter bereitstellt. Wenn Sie einen stabilen Schlüssel angeben, bleibt der Artikelstatus bei Änderungen am Datensatz konsistent:
LazyColumn { items( items = messages, key = { message -> // Return a stable + unique key for the item message.id } ) { message -> MessageRow(message) } }
Durch die Angabe von Schlüsseln kann Compose Umordnungen korrekt verarbeiten. Wenn Ihr Element beispielsweise einen gespeicherten Status enthält, können Sie mit dem Festlegen von Schlüsseln dafür sorgen, dass Compose diesen Status zusammen mit dem Element verschiebt, wenn sich seine Position ändert.
LazyColumn { items(books, key = { it.id }) { val rememberedValue = remember { Random.nextInt() } } }
Es gibt jedoch eine Einschränkung hinsichtlich der Typen, die Sie als Elementschlüssel verwenden können.
Der Typ des Schlüssels muss von Bundle
unterstützt werden. Das ist der Android-Mechanismus zum Beibehalten der Status, wenn die Aktivität neu erstellt wird. Bundle
unterstützt Typen wie Primitives, Enums oder Parcelables.
LazyColumn { items(books, key = { // primitives, enums, Parcelable, etc. }) { // ... } }
Der Schlüssel muss von Bundle
unterstützt werden, damit der rememberSaveable
im zusammensetzbaren Element wiederhergestellt werden kann, wenn die Aktivität neu erstellt wird oder wenn Sie von diesem Element weg- und wieder zurückscrollen.
LazyColumn { items(books, key = { it.id }) { val rememberedValue = rememberSaveable { Random.nextInt() } } }
Elementanimationen
Wenn Sie das RecyclerView-Widget verwendet haben, wissen Sie, dass es Änderungen an Elementen automatisch animiert.
Lazy Layouts bieten dieselbe Funktionalität für das Neuanordnen von Elementen.
Die API ist einfach – Sie müssen nur den Modifikator animateItem
für den Artikelinhalt festlegen:
LazyColumn { // It is important to provide a key to each item to ensure animateItem() works as expected. items(books, key = { it.id }) { Row(Modifier.animateItem()) { // ... } } }
Sie können sogar eine benutzerdefinierte Animationsspezifikation angeben, wenn Sie Folgendes benötigen:
LazyColumn { items(books, key = { it.id }) { Row( Modifier.animateItem( fadeInSpec = tween(durationMillis = 250), fadeOutSpec = tween(durationMillis = 100), placementSpec = spring(stiffness = Spring.StiffnessLow, dampingRatio = Spring.DampingRatioMediumBouncy) ) ) { // ... } } }
Sie müssen Schlüssel für Ihre Elemente angeben, damit die neue Position für das verschobene Element gefunden werden kann.
Beispiel: Elemente in Lazy Lists animieren
Mit Compose können Sie Änderungen an Elementen in Lazy-Listen animieren. In Kombination implementieren die folgenden Snippets Animationen beim Hinzufügen, Entfernen und Neuanordnen von Elementen in Lazy Lists.
In diesem Snippet wird eine Liste von Strings mit animierten Übergängen angezeigt, wenn Elemente hinzugefügt, entfernt oder neu angeordnet werden:
@Composable fun ListAnimatedItems( items: List<String>, modifier: Modifier = Modifier ) { LazyColumn(modifier) { // Use a unique key per item, so that animations work as expected. items(items, key = { it }) { ListItem( headlineContent = { Text(it) }, modifier = Modifier .animateItem( // Optionally add custom animation specs ) .fillParentMaxWidth() .padding(horizontal = 8.dp, vertical = 0.dp), ) } } }
Wichtige Punkte zum Code
ListAnimatedItems
zeigt eine Liste von Strings in einemLazyColumn
mit animierten Übergängen an, wenn Elemente geändert werden.- Mit der Funktion
items
wird jedem Element in der Liste ein eindeutiger Schlüssel zugewiesen. Compose verwendet die Schlüssel, um die Elemente zu verfolgen und Änderungen an ihren Positionen zu erkennen. - Mit
ListItem
wird das Layout der einzelnen Listenelemente definiert. Es wird einheadlineContent
-Parameter verwendet, der den Hauptinhalt des Artikels definiert. - Mit dem Modifikator
animateItem
werden Standardanimationen auf das Hinzufügen, Entfernen und Verschieben von Elementen angewendet.
Das folgende Snippet zeigt einen Bildschirm mit Steuerelementen zum Hinzufügen und Entfernen von Elementen sowie zum Sortieren einer vordefinierten Liste:
@Composable private fun ListAnimatedItemsExample( data: List<String>, modifier: Modifier = Modifier, onAddItem: () -> Unit = {}, onRemoveItem: () -> Unit = {}, resetOrder: () -> Unit = {}, onSortAlphabetically: () -> Unit = {}, onSortByLength: () -> Unit = {}, ) { val canAddItem = data.size < 10 val canRemoveItem = data.isNotEmpty() Scaffold(modifier) { paddingValues -> Column( modifier = Modifier .padding(paddingValues) .fillMaxSize() ) { // Buttons that change the value of displayedItems. AddRemoveButtons(canAddItem, canRemoveItem, onAddItem, onRemoveItem) OrderButtons(resetOrder, onSortAlphabetically, onSortByLength) // List that displays the values of displayedItems. ListAnimatedItems(data) } } }
Wichtige Punkte zum Code
ListAnimatedItemsExample
– Auf dem Bildschirm werden Steuerelemente zum Hinzufügen, Entfernen und Sortieren von Elementen angezeigt.onAddItem
undonRemoveItem
sind Lambda-Ausdrücke, die anAddRemoveButtons
übergeben werden, um Elemente zur Liste hinzuzufügen und daraus zu entfernen.resetOrder
,onSortAlphabetically
undonSortByLength
sind Lambda-Ausdrücke, die anOrderButtons
übergeben werden, um die Reihenfolge der Elemente in der Liste zu ändern.
- In
AddRemoveButtons
werden die Schaltflächen „Hinzufügen“ und „Entfernen“ angezeigt. Damit werden die Schaltflächen aktiviert bzw. deaktiviert und Schaltflächenklicks verarbeitet. OrderButtons
zeigt die Schaltflächen zum Neuordnen der Liste an. Sie empfängt die Lambda-Funktionen zum Zurücksetzen der Reihenfolge und zum Sortieren der Liste nach Länge oder alphabetisch.ListAnimatedItems
ruft die zusammensetzbare FunktionListAnimatedItems
auf und übergibt die Listedata
, um die animierte Liste von Strings anzuzeigen.data
ist an anderer Stelle definiert.
Mit diesem Snippet wird eine Benutzeroberfläche mit den Schaltflächen Element hinzufügen und Element löschen erstellt:
@Composable private fun AddRemoveButtons( canAddItem: Boolean, canRemoveItem: Boolean, onAddItem: () -> Unit, onRemoveItem: () -> Unit ) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center ) { Button(enabled = canAddItem, onClick = onAddItem) { Text("Add Item") } Spacer(modifier = Modifier.padding(25.dp)) Button(enabled = canRemoveItem, onClick = onRemoveItem) { Text("Delete Item") } } }
Wichtige Punkte zum Code
- In
AddRemoveButtons
wird eine Zeile mit Schaltflächen zum Hinzufügen und Entfernen von Elementen in der Liste angezeigt. - Mit den Parametern
canAddItem
undcanRemoveItem
wird der aktivierte Status der Schaltflächen gesteuert. WenncanAddItem
odercanRemoveItem
„false“ ist, wird die entsprechende Schaltfläche deaktiviert. - Die Parameter
onAddItem
undonRemoveItem
sind Lambdas, die ausgeführt werden, wenn der Nutzer auf die entsprechende Schaltfläche klickt.
Schließlich werden in diesem Snippet drei Schaltflächen zum Sortieren der Liste angezeigt (Zurücksetzen, Alphabetisch und Länge):
@Composable private fun OrderButtons( resetOrder: () -> Unit, orderAlphabetically: () -> Unit, orderByLength: () -> Unit ) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center ) { var selectedIndex by remember { mutableIntStateOf(0) } val options = listOf("Reset", "Alphabetical", "Length") SingleChoiceSegmentedButtonRow { options.forEachIndexed { index, label -> SegmentedButton( shape = SegmentedButtonDefaults.itemShape( index = index, count = options.size ), onClick = { Log.d("AnimatedOrderedList", "selectedIndex: $selectedIndex") selectedIndex = index when (options[selectedIndex]) { "Reset" -> resetOrder() "Alphabetical" -> orderAlphabetically() "Length" -> orderByLength() } }, selected = index == selectedIndex ) { Text(label) } } } } }
Wichtige Punkte zum Code
- In
OrderButtons
wird einSingleChoiceSegmentedButtonRow
angezeigt, damit Nutzer eine Sortiermethode für die Liste auswählen oder die Listenreihenfolge zurücksetzen können. Mit der Komponente ASegmentedButton
können Sie eine einzelne Option aus einer Liste von Optionen auswählen. resetOrder
,orderAlphabetically
undorderByLength
sind Lambda-Funktionen, die ausgeführt werden, wenn die entsprechende Schaltfläche ausgewählt wird.- Die Statusvariable
selectedIndex
verfolgt die ausgewählte Option.
Ergebnis
In diesem Video sehen Sie das Ergebnis der vorherigen Snippets, wenn Elemente neu angeordnet werden:
Fixierte Header (experimentell)
Das Muster „Fixierter Header“ ist hilfreich, wenn Listen mit gruppierten Daten angezeigt werden. Unten sehen Sie ein Beispiel für eine Kontaktliste, die nach dem Anfangsbuchstaben der einzelnen Kontakte gruppiert ist:
Um mit LazyColumn
einen fixierten Header zu erstellen, können Sie die experimentelle Funktion stickyHeader()
verwenden und den Headerinhalt angeben:
@OptIn(ExperimentalFoundationApi::class) @Composable fun ListWithHeader(items: List<Item>) { LazyColumn { stickyHeader { Header() } items(items) { item -> ItemRow(item) } } }
Wenn Sie eine Liste mit mehreren Headern wie im Beispiel „Kontaktliste“ oben erstellen möchten, können Sie Folgendes tun:
// This ideally would be done in the ViewModel val grouped = contacts.groupBy { it.firstName[0] } @OptIn(ExperimentalFoundationApi::class) @Composable fun ContactsList(grouped: Map<Char, List<Contact>>) { LazyColumn { grouped.forEach { (initial, contactsForInitial) -> stickyHeader { CharacterHeader(initial) } items(contactsForInitial) { contact -> ContactListItem(contact) } } } }
Auf die Scrollposition reagieren
Viele Apps müssen auf Änderungen der Scrollposition und des Elementlayouts reagieren.
Die Lazy-Komponenten unterstützen diesen Anwendungsfall, indem sie LazyListState
hochladen:
@Composable fun MessageList(messages: List<Message>) { // Remember our own LazyListState val listState = rememberLazyListState() // Provide it to LazyColumn LazyColumn(state = listState) { // ... } }
Bei einfachen Anwendungsfällen müssen Apps in der Regel nur Informationen zum ersten sichtbaren Element kennen. Dazu stellt LazyListState
die Properties firstVisibleItemIndex
und firstVisibleItemScrollOffset
bereit.
Wenn wir das Beispiel verwenden, in dem eine Schaltfläche ein- und ausgeblendet wird, je nachdem, ob der Nutzer am ersten Element vorbeigescrollt hat:
@Composable fun MessageList(messages: List<Message>) { Box { val listState = rememberLazyListState() LazyColumn(state = listState) { // ... } // Show the button if the first visible item is past // the first item. We use a remembered derived state to // minimize unnecessary compositions val showButton by remember { derivedStateOf { listState.firstVisibleItemIndex > 0 } } AnimatedVisibility(visible = showButton) { ScrollToTopButton() } } }
Das direkte Lesen des Status in der Komposition ist nützlich, wenn Sie andere UI-Composables aktualisieren müssen. Es gibt aber auch Szenarien, in denen das Ereignis nicht in derselben Komposition verarbeitet werden muss. Ein häufiges Beispiel hierfür ist das Senden eines Analytics-Ereignisses, sobald der Nutzer einen bestimmten Punkt auf der Seite erreicht hat. Um dies effizient zu handhaben, können wir ein snapshotFlow()
verwenden:
val listState = rememberLazyListState() LazyColumn(state = listState) { // ... } LaunchedEffect(listState) { snapshotFlow { listState.firstVisibleItemIndex } .map { index -> index > 0 } .distinctUntilChanged() .filter { it } .collect { MyAnalyticsService.sendScrolledPastFirstItemEvent() } }
LazyListState
stellt über die Eigenschaft layoutInfo
auch Informationen zu allen aktuell angezeigten Elementen und deren Grenzen auf dem Bildschirm bereit. Weitere Informationen finden Sie in der Klasse LazyListLayoutInfo
.
Scrollposition steuern
Es ist nicht nur wichtig, dass Apps auf die Scrollposition reagieren können, sondern auch, dass sie die Scrollposition steuern können.
LazyListState
unterstützt dies über die Funktion scrollToItem()
, die die Scrollposition „sofort“ ändert, und animateScrollToItem()
, die mit einer Animation scrollt (auch als „weiches Scrollen“ bezeichnet):
@Composable fun MessageList(messages: List<Message>) { val listState = rememberLazyListState() // Remember a CoroutineScope to be able to launch val coroutineScope = rememberCoroutineScope() LazyColumn(state = listState) { // ... } ScrollToTopButton( onClick = { coroutineScope.launch { // Animate scroll to the first item listState.animateScrollToItem(index = 0) } } ) }
Große Datasets (Paging)
Mit der Paging Library können Apps große Listen von Elementen unterstützen, indem sie bei Bedarf kleine Teile der Liste laden und anzeigen. Paging 3.0 und höher bietet Compose-Unterstützung über die androidx.paging:paging-compose
-Bibliothek.
Um eine Liste mit paginiertem Inhalt anzuzeigen, können wir die Erweiterungsfunktion collectAsLazyPagingItems()
verwenden und dann das zurückgegebene LazyPagingItems
an items()
in unserem LazyColumn
übergeben. Ähnlich wie bei der Unterstützung der Paginierung in Ansichten können Sie Platzhalter anzeigen, während Daten geladen werden. Prüfen Sie dazu, ob item
gleich null
ist:
@Composable fun MessageList(pager: Pager<Int, Message>) { val lazyPagingItems = pager.flow.collectAsLazyPagingItems() LazyColumn { items( lazyPagingItems.itemCount, key = lazyPagingItems.itemKey { it.id } ) { index -> val message = lazyPagingItems[index] if (message != null) { MessageRow(message) } else { MessagePlaceholder() } } } }
Tipps zur Verwendung von Lazy Layouts
Es gibt einige Tipps, die Sie beachten können, damit Ihre Lazy-Layouts wie vorgesehen funktionieren.
Verwenden Sie keine Elemente mit einer Größe von 0 Pixeln.
Das kann beispielsweise passieren, wenn Sie erwarten, dass Sie einige Daten wie Bilder asynchron abrufen, um die Elemente Ihrer Liste zu einem späteren Zeitpunkt zu füllen. Dadurch würde das Lazy-Layout alle Elemente in der ersten Messung zusammensetzen, da ihre Höhe 0 Pixel beträgt und alle in den Darstellungsbereich passen. Sobald die Elemente geladen und ihre Höhe erweitert wurde, werden alle anderen Elemente, die beim ersten Mal unnötigerweise zusammengesetzt wurden, von Lazy-Layouts verworfen, da sie nicht in den Darstellungsbereich passen. Um dies zu vermeiden, sollten Sie für Ihre Elemente eine Standardgröße festlegen, damit das Lazy-Layout richtig berechnen kann, wie viele Elemente tatsächlich in den Viewport passen:
@Composable fun Item(imageUrl: String) { AsyncImage( model = rememberAsyncImagePainter(model = imageUrl), modifier = Modifier.size(30.dp), contentDescription = null // ... ) }
Wenn Sie die ungefähre Größe Ihrer Elemente kennen, nachdem die Daten asynchron geladen wurden, sollten Sie dafür sorgen, dass die Größe der Elemente vor und nach dem Laden gleich bleibt. Fügen Sie dazu beispielsweise einige Platzhalter hinzu. So wird die richtige Scrollposition beibehalten.
Verschachtelung von Komponenten vermeiden, die in dieselbe Richtung gescrollt werden können
Dies gilt nur für Fälle, in denen verschachtelte untergeordnete Elemente ohne vordefinierte Größe in einem anderen übergeordneten Element mit Scrollfunktion in derselben Richtung enthalten sind. Beispiel: Versuch, ein untergeordnetes LazyColumn
ohne feste Höhe in ein vertikal scrollbares Column
-Übergeordnetes Element einzufügen:
// throws IllegalStateException Column( modifier = Modifier.verticalScroll(state) ) { LazyColumn { // ... } }
Stattdessen kann dasselbe Ergebnis erzielt werden, indem Sie alle Composables in ein übergeordnetes LazyColumn
einfügen und dessen DSL verwenden, um verschiedene Arten von Inhalten zu übergeben. So können Sie einzelne Elemente sowie mehrere Listenelemente an einem Ort ausgeben:
LazyColumn { item { Header() } items(data) { item -> PhotoItem(item) } item { Footer() } }
Beachten Sie, dass Fälle, in denen Sie verschiedene Richtungslayouts verschachteln, z. B. ein scrollbares übergeordnetes Row
und ein untergeordnetes LazyColumn
, zulässig sind:
Row( modifier = Modifier.horizontalScroll(scrollState) ) { LazyColumn { // ... } }
sowie Fälle, in denen Sie weiterhin dieselben Richtungslayouts verwenden, aber auch eine feste Größe für die verschachtelten untergeordneten Elemente festlegen:
Column( modifier = Modifier.verticalScroll(scrollState) ) { LazyColumn( modifier = Modifier.height(200.dp) ) { // ... } }
Mehrere Elemente in einem Element vermeiden
In diesem Beispiel gibt die Lambda-Funktion des zweiten Elements zwei Elemente in einem Block aus:
LazyVerticalGrid( columns = GridCells.Adaptive(100.dp) ) { item { Item(0) } item { Item(1) Item(2) } item { Item(3) } // ... }
Bei Lazy-Layouts wird dies wie erwartet gehandhabt. Die Elemente werden nacheinander angeordnet, als wären es unterschiedliche Elemente. Dabei gibt es jedoch einige Probleme.
Wenn mehrere Elemente als Teil eines Elements ausgegeben werden, werden sie als eine Einheit behandelt. Das bedeutet, dass sie nicht mehr einzeln zusammengesetzt werden können. Wenn ein Element auf dem Bildschirm sichtbar wird, müssen alle Elemente, die dem Element entsprechen, zusammengesetzt und gemessen werden. Bei übermäßigem Gebrauch kann dies die Leistung beeinträchtigen. Wenn Sie alle Elemente in ein Element einfügen, wird der Zweck der Verwendung von Lazy-Layouts vollständig zunichte gemacht. Abgesehen von potenziellen Leistungsproblemen beeinträchtigt das Einfügen mehrerer Elemente in ein Element auch scrollToItem()
und animateScrollToItem()
.
Es gibt jedoch gültige Anwendungsfälle für das Einfügen mehrerer Elemente in ein Element, z. B. Trennzeichen in einer Liste. Trennzeichen sollen die Scrollindexe nicht ändern, da sie nicht als unabhängige Elemente betrachtet werden sollten. Da Trennzeichen klein sind, wird die Leistung nicht beeinträchtigt. Eine Trennlinie muss wahrscheinlich sichtbar sein, wenn das Element davor sichtbar ist. Sie kann also Teil des vorherigen Elements sein:
LazyVerticalGrid( columns = GridCells.Adaptive(100.dp) ) { item { Item(0) } item { Item(1) Divider() } item { Item(2) } // ... }
Benutzerdefinierte Arrangements verwenden
Lazy Lists haben in der Regel viele Elemente und nehmen mehr Platz ein als der Scrolling-Container. Wenn Ihre Liste nur wenige Elemente enthält, kann es sein, dass Ihr Design spezifischere Anforderungen an die Positionierung dieser Elemente im Viewport hat.
Dazu können Sie eine benutzerdefinierte Branche Arrangement
verwenden und sie an LazyColumn
übergeben. Im folgenden Beispiel muss das TopWithFooter
-Objekt nur die arrange
-Methode implementieren. Erstens werden Elemente nacheinander positioniert. Zweitens: Wenn die insgesamt verwendete Höhe geringer als die Höhe des Viewports ist, wird die Fußzeile unten positioniert:
object TopWithFooter : Arrangement.Vertical { override fun Density.arrange( totalSize: Int, sizes: IntArray, outPositions: IntArray ) { var y = 0 sizes.forEachIndexed { index, size -> outPositions[index] = y y += size } if (y < totalSize) { val lastIndex = outPositions.lastIndex outPositions[lastIndex] = totalSize - sizes.last() } } }
Fügen Sie contentType
hinzu.
Ab Compose 1.2 können Sie die Leistung Ihres Lazy-Layouts maximieren, indem Sie Ihren Listen oder Grids contentType
hinzufügen. So können Sie den Inhaltstyp für jedes Element des Layouts angeben, wenn Sie eine Liste oder ein Raster aus mehreren verschiedenen Elementtypen erstellen:
LazyColumn { items(elements, contentType = { it.type }) { // ... } }
Wenn Sie contentType
angeben, kann Compose Kompositionen nur zwischen Elementen desselben Typs wiederverwenden. Die Wiederverwendung ist effizienter, wenn Sie Elemente mit ähnlicher Struktur erstellen. Durch die Angabe der Inhaltstypen wird sichergestellt, dass Compose nicht versucht, ein Element vom Typ A auf ein völlig anderes Element vom Typ B zu setzen. So können Sie die Vorteile der Kompositionswiederverwendung und die Leistung Ihres Lazy-Layouts maximieren.
Leistung messen
Die Leistung eines Lazy-Layouts lässt sich nur zuverlässig messen, wenn es im Release-Modus und mit aktivierter R8-Optimierung ausgeführt wird. In Debug-Builds kann das Scrollen in Lazy-Layouts langsamer erscheinen. Weitere Informationen finden Sie unter Compose-Leistung.
Zusätzliche Ressourcen
- Endlose scrollbare Liste erstellen
- Scrollbares Raster erstellen
- Verschachtelte Scrolling-Elemente in einer Liste anzeigen
- Liste während der Eingabe filtern
- Daten mit Listen und Paging verzögert laden
- Liste mit mehreren Elementtypen erstellen
- Video: Listen in Compose
Empfehlungen für dich
- Hinweis: Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
RecyclerView
zu Lazy List migrieren- UI-Status in Compose speichern
- Kotlin für Jetpack Compose