Wiele aplikacji musi wyświetlać kolekcje elementów. Z tego dokumentu dowiesz się, jak skutecznie to zrobić w Jetpack Compose.
Jeśli wiesz, że Twój przypadek użycia nie wymaga przewijania, możesz użyć prostego elementu Column
lub Row
(w zależności od kierunku) i wyemitować zawartość każdego elementu, iterując listę w ten sposób:
@Composable fun MessageList(messages: List<Message>) { Column { messages.forEach { message -> MessageRow(message) } } }
Możemy sprawić, że element Column
będzie przewijany, używając modyfikatora verticalScroll()
.
Lazy lists
Jeśli musisz wyświetlić dużą liczbę elementów (lub listę o nieznanej długości), użycie układu takiego jak Column
może powodować problemy z wydajnością, ponieważ wszystkie elementy będą komponowane i układane niezależnie od tego, czy są widoczne.
Compose udostępnia zestaw komponentów, które tworzą i układają tylko te elementy, które są widoczne w obszarze widoku komponentu. Te komponenty to:LazyColumn
iLazyRow
.
Jak sama nazwa wskazuje, różnica między
LazyColumn
a
LazyRow
polega na orientacji, w której wyświetlają elementy i umożliwiają przewijanie. LazyColumn
– tworzy listę przewijaną w pionie, a LazyRow
– listę przewijaną w poziomie.
Komponenty Lazy różnią się od większości układów w Compose. Zamiast akceptować parametr @Composable
bloku treści, który umożliwia aplikacjom bezpośrednie emitowanie komponentów, komponenty Lazy udostępniają LazyListScope.()
blok. Ten
LazyListScope
blok zawiera język DSL, który umożliwia aplikacjom opisanie zawartości produktu. Komponent Lazy jest następnie odpowiedzialny za dodawanie treści każdego elementu zgodnie z układem i pozycją przewijania.
LazyListScope
DSL
Język DSL LazyListScope
udostępnia szereg funkcji do opisywania elementów w układzie. W najprostszym przypadku polecenie
item()
dodaje jeden element, a polecenie
items(Int)
dodaje wiele elementów:
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") } }
Istnieje też wiele funkcji rozszerzeń, które umożliwiają dodawanie kolekcji elementów, np. List
. Te rozszerzenia umożliwiają łatwe przeniesienie powyższego przykładu Column
:
/** * import androidx.compose.foundation.lazy.items */ LazyColumn { items(messages) { message -> MessageRow(message) } }
Istnieje też wariant funkcji rozszerzenia items()
o nazwie itemsIndexed()
, który podaje indeks. Więcej informacji znajdziesz w LazyListScope
.
Leniwe siatki
Kompozycje
LazyVerticalGrid
i
LazyHorizontalGrid
umożliwiają wyświetlanie elementów w siatce. Kompozycja LazyVerticalGrid
wyświetla elementy w kontenerze przewijanym w pionie, rozciągniętym na
wiele kolumn, a kompozycje LazyHorizontalGrid zachowują się tak samo
na osi poziomej.
Siatki mają te same zaawansowane możliwości interfejsu API co listy i korzystają z bardzo podobnego języka DSL –LazyGridScope.()
do opisywania treści.
Parametr columns
w LazyVerticalGrid
i parametr rows
w LazyHorizontalGrid
określają, jak komórki są tworzone w kolumnach lub wierszach. W tym przykładzie elementy są wyświetlane w siatce, a za pomocą GridCells.Adaptive
ustawiono szerokość każdej kolumny na co najmniej 128.dp
:
LazyVerticalGrid( columns = GridCells.Adaptive(minSize = 128.dp) ) { items(photos) { photo -> PhotoItem(photo) } }
LazyVerticalGrid
umożliwia określenie szerokości elementów, a następnie siatka dopasuje jak najwięcej kolumn. Pozostała szerokość jest rozdzielana po równo między kolumny po obliczeniu ich liczby.
Ten adaptacyjny sposób określania rozmiaru jest szczególnie przydatny do wyświetlania zestawów elementów na ekranach o różnych rozmiarach.
Jeśli znasz dokładną liczbę kolumn, których chcesz użyć, możesz zamiast tego podać instancję GridCells.Fixed
zawierającą liczbę wymaganych kolumn.
Jeśli Twój projekt wymaga, aby tylko niektóre elementy miały niestandardowe wymiary, możesz użyć obsługi siatki, aby podać niestandardowe zakresy kolumn dla elementów.
Określ zakres kolumn za pomocą parametru span
metod
LazyGridScope DSL
item
i items
.
maxLineSpan
, jedna z wartości zakresu, jest szczególnie przydatna, gdy używasz
rozmiaru adaptacyjnego, ponieważ liczba kolumn nie jest stała.
Ten przykład pokazuje, jak podać pełny zakres wiersza:
LazyVerticalGrid( columns = GridCells.Adaptive(minSize = 30.dp) ) { item(span = { // LazyGridItemSpanScope: // maxLineSpan GridItemSpan(maxLineSpan) }) { CategoryCard("Fruits") } // ... }
Leniwa siatka kaskadowa
LazyVerticalStaggeredGrid
i LazyHorizontalStaggeredGrid
to funkcje kompozycyjne, które umożliwiają tworzenie leniwie wczytywanej, rozłożonej w czasie siatki elementów.
LazyVerticalStaggeredGrid wyświetla elementy w kontenerze z możliwością przewijania w pionie, który obejmuje wiele kolumn i umożliwia poszczególnym elementom przyjmowanie różnych wysokości. Lenistwo w przypadku siatek poziomych działa tak samo na osi poziomej w przypadku elementów o różnych szerokościach.
Poniższy fragment kodu to podstawowy przykład użycia LazyVerticalStaggeredGrid
z 200.dp
szerokością na 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() )
Aby ustawić stałą liczbę kolumn, możesz użyć StaggeredGridCells.Fixed(columns)
zamiast StaggeredGridCells.Adaptive
.
Dzieli dostępną szerokość przez liczbę kolumn (lub wierszy w przypadku siatki poziomej) i sprawia, że każdy element zajmuje tę szerokość (lub wysokość w przypadku siatki poziomej):
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() )

Wypełnienie treści
Czasami trzeba dodać do treści dopełnienie. Komponenty lazy
umożliwiają przekazywanie niektórych
PaddingValues
do parametru contentPadding
:
LazyColumn( contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp), ) { // ... }
W tym przykładzie dodamy 16.dp
dopełnienia do krawędzi poziomych (lewej i prawej), a następnie 8.dp
do góry i dołu treści.
Pamiętaj, że ten margines jest stosowany do treści, a nie do samego elementu LazyColumn
. W powyższym przykładzie pierwszy element doda 8.dp
do góry, ostatni element doda 8.dp
do dołu, a wszystkie elementy
będą miały 16.dp
po lewej i prawej stronie.
Możesz też przekazać Scaffold
's PaddingValues
do
LazyColumn
's contentPadding
. Zapoznaj się z przewodnikiem dotyczącym wyświetlania od krawędzi do krawędzi.
Odstępy między treściami
Aby dodać odstępy między elementami, możesz użyć elementu Arrangement.spacedBy()
.
W przykładzie poniżej dodano 4.dp
spacji między poszczególnymi elementami:
LazyColumn( verticalArrangement = Arrangement.spacedBy(4.dp), ) { // ... }
Podobnie w przypadku LazyRow
:
LazyRow( horizontalArrangement = Arrangement.spacedBy(4.dp), ) { // ... }
Siatki akceptują jednak zarówno układ pionowy, jak i poziomy:
LazyVerticalGrid( columns = GridCells.Fixed(2), verticalArrangement = Arrangement.spacedBy(16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp) ) { items(photos) { item -> PhotoItem(item) } }
Klucze elementów
Domyślnie stan każdego elementu jest powiązany z jego pozycją na liście lub w siatce. Może to jednak powodować problemy, jeśli zbiór danych ulegnie zmianie, ponieważ elementy, które zmienią pozycję, utracą zapamiętany stan. Wyobraź sobie sytuację, w której LazyRow
znajduje się w LazyColumn
. Jeśli pozycja elementu w wierszu ulegnie zmianie, użytkownik utraci pozycję przewijania w wierszu.
Aby temu zapobiec, możesz podać stabilny i niepowtarzalny klucz dla każdego produktu, podając blok do parametru key
. Podanie stabilnego klucza umożliwia zachowanie spójności stanu elementu w przypadku zmian w zbiorze danych:
LazyColumn { items( items = messages, key = { message -> // Return a stable + unique key for the item message.id } ) { message -> MessageRow(message) } }
Dzięki podaniu kluczy Compose może prawidłowo obsługiwać zmiany kolejności. Jeśli na przykład element zawiera zapamiętany stan, ustawienie kluczy umożliwi bibliotece Compose przeniesienie tego stanu wraz z elementem, gdy zmieni się jego pozycja.
LazyColumn { items(books, key = { it.id }) { val rememberedValue = remember { Random.nextInt() } } }
Istnieje jednak jedno ograniczenie dotyczące typów, których można używać jako kluczy produktów.
Typ klucza musi być obsługiwany przez Bundle
, czyli mechanizm Androida, który zachowuje stany, gdy aktywność jest ponownie tworzona. Bundle
obsługuje typy takie jak typy proste, wyliczenia czy obiekty Parcelable.
LazyColumn { items(books, key = { // primitives, enums, Parcelable, etc. }) { // ... } }
Klucz musi być obsługiwany przez Bundle
, aby można było przywrócić rememberSaveable
w komponencie elementu, gdy aktywność zostanie utworzona ponownie, a nawet gdy przewiniesz widok poza ten element i wrócisz do niego.
LazyColumn { items(books, key = { it.id }) { val rememberedValue = rememberSaveable { Random.nextInt() } } }
Animacje elementów
Jeśli używasz widżetu RecyclerView, wiesz, że animuje on zmiany elementów automatycznie.
Układy leniwe zapewniają taką samą funkcjonalność w przypadku zmiany kolejności elementów.
Interfejs API jest prosty – wystarczy ustawić modyfikator
animateItem
w treści produktu:
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()) { // ... } } }
W razie potrzeby możesz nawet podać niestandardową specyfikację animacji:
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) ) ) { // ... } } }
Pamiętaj, aby podać klucze dla elementów, aby można było znaleźć nową pozycję przeniesionego elementu.
Przykład: animowanie elementów na listach ładowanych na żądanie
Za pomocą Compose możesz animować zmiany elementów na listach wczytywanych na żądanie. Poniższe fragmenty kodu razem implementują animacje podczas dodawania, usuwania i zmiany kolejności elementów listy leniwej.
Ten fragment kodu wyświetla listę ciągów znaków z animowanymi przejściami, gdy elementy są dodawane, usuwane lub zmieniana jest ich kolejność:
@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), ) } } }
Najważniejsze informacje o kodzie
ListAnimatedItems
wyświetla listę ciągów znaków wLazyColumn
z animowanymi przejściami, gdy elementy są modyfikowane.- Funkcja
items
przypisuje unikalny klucz do każdego elementu na liście. Compose używa kluczy do śledzenia elementów i identyfikowania zmian w ich pozycjach. ListItem
określa układ każdego elementu listy. Przyjmuje parametrheadlineContent
, który określa główną treść produktu.- Modyfikator
animateItem
stosuje domyślne animacje do dodawania, usuwania i przenoszenia elementów.
Poniższy fragment kodu przedstawia ekran z elementami sterującymi do dodawania i usuwania elementów oraz sortowania predefiniowanej listy:
@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) } } }
Najważniejsze informacje o kodzie
ListAnimatedItemsExample
wyświetla ekran z elementami sterującymi do dodawania, usuwania i sortowania elementów.onAddItem
ionRemoveItem
to wyrażenia lambda przekazywane do funkcjiAddRemoveButtons
w celu dodawania i usuwania elementów z listy.resetOrder
,onSortAlphabetically
ionSortByLength
to wyrażenia lambda, które są przekazywane do funkcjiOrderButtons
, aby zmienić kolejność elementów na liście.
AddRemoveButtons
wyświetla przyciski „Dodaj” i „Usuń”. Włącza i wyłącza przyciski oraz obsługuje ich kliknięcia.OrderButtons
wyświetla przyciski do zmiany kolejności na liście. Otrzymuje funkcje lambda do resetowania kolejności i sortowania listy według długości lub alfabetycznie.ListAnimatedItems
wywołuje funkcję kompozycyjnąListAnimatedItems
, przekazując listędata
, aby wyświetlić animowaną listę ciągów znaków.data
jest zdefiniowana w innym miejscu.
Ten fragment kodu tworzy interfejs z przyciskami Dodaj element i Usuń element:
@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") } } }
Najważniejsze informacje o kodzie
AddRemoveButtons
wyświetla wiersz przycisków umożliwiających dodawanie i usuwanie elementów z listy.- Parametry
canAddItem
icanRemoveItem
określają stan włączenia przycisków. Jeśli wartościcanAddItem
lubcanRemoveItem
to fałsz, odpowiedni przycisk jest wyłączony. - Parametry
onAddItem
ionRemoveItem
to funkcje lambda, które są wykonywane, gdy użytkownik kliknie odpowiedni przycisk.
Na koniec ten fragment kodu wyświetla 3 przyciski do sortowania listy: Resetuj, Alfabetycznie i Długość.
@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) } } } } }
Najważniejsze informacje o kodzie
OrderButtons
wyświetlaSingleChoiceSegmentedButtonRow
, aby umożliwić użytkownikom wybór metody sortowania na liście lub zresetowanie kolejności listy. Komponent ASegmentedButton
umożliwia wybranie jednej opcji z listy.resetOrder
,orderAlphabetically
iorderByLength
to funkcje lambda, które są wykonywane po wybraniu odpowiedniego przycisku.- Zmienna stanu
selectedIndex
śledzi wybraną opcję.
Wynik
Ten film pokazuje wynik działania powyższych fragmentów kodu po zmianie kolejności elementów:
Przyklejone nagłówki (funkcja eksperymentalna)
Wzorzec „przyklejonego nagłówka” jest przydatny podczas wyświetlania list pogrupowanych danych. Poniżej znajdziesz przykład „listy kontaktów” pogrupowanej według inicjałów poszczególnych kontaktów:
Aby uzyskać przyklejony nagłówek z LazyColumn
, możesz użyć eksperymentalnej funkcji stickyHeader()
LazyColumn
, podając treść nagłówka:
@OptIn(ExperimentalFoundationApi::class) @Composable fun ListWithHeader(items: List<Item>) { LazyColumn { stickyHeader { Header() } items(items) { item -> ItemRow(item) } } }
Aby uzyskać listę z wieloma nagłówkami, taką jak w przykładzie „lista kontaktów” powyżej, możesz wykonać te czynności:
// 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) } } } }
Reagowanie na pozycję przewijania
Wiele aplikacji musi reagować na zmiany pozycji przewijania i układu elementów.
Komponenty Lazy obsługują ten przypadek użycia przez przeniesienie elementu LazyListState
:
@Composable fun MessageList(messages: List<Message>) { // Remember our own LazyListState val listState = rememberLazyListState() // Provide it to LazyColumn LazyColumn(state = listState) { // ... } }
W przypadku prostych zastosowań aplikacje zwykle potrzebują tylko informacji o pierwszym widocznym elemencie. W tym celu usługa
LazyListState
udostępnia właściwości
firstVisibleItemIndex
i
firstVisibleItemScrollOffset
.
Jeśli użyjemy przykładu wyświetlania i ukrywania przycisku w zależności od tego, czy użytkownik przewinął widok poza pierwszy element:
@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() } } }
Odczytywanie stanu bezpośrednio w kompozycji jest przydatne, gdy musisz zaktualizować inne komponenty interfejsu, ale są też sytuacje, w których zdarzenie nie musi być obsługiwane w tej samej kompozycji. Typowym przykładem jest wysyłanie zdarzenia Analytics, gdy użytkownik przewinie stronę do określonego miejsca. Aby skutecznie sobie z tym poradzić, możemy użyć snapshotFlow()
:
val listState = rememberLazyListState() LazyColumn(state = listState) { // ... } LaunchedEffect(listState) { snapshotFlow { listState.firstVisibleItemIndex } .map { index -> index > 0 } .distinctUntilChanged() .filter { it } .collect { MyAnalyticsService.sendScrolledPastFirstItemEvent() } }
LazyListState
udostępnia też informacje o wszystkich aktualnie wyświetlanych elementach i ich granicach na ekranie za pomocą właściwości layoutInfo
. Więcej informacji znajdziesz w sekcji dotyczącej klasy LazyListLayoutInfo
.
Kontrolowanie pozycji przewijania
Oprócz reagowania na pozycję przewijania przydatna jest też możliwość kontrolowania tej pozycji przez aplikacje.
LazyListState
obsługuje to za pomocą funkcji scrollToItem()
, która „natychmiast” zmienia pozycję przewijania, oraz funkcji animateScrollToItem()
, która przewija za pomocą animacji (tzw. płynne przewijanie):
@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) } } ) }
Duże zbiory danych (stronicowanie)
Biblioteka Paging umożliwia aplikacjom obsługę dużych list elementów, wczytywanie i wyświetlanie małych fragmentów listy w razie potrzeby. Biblioteka androidx.paging:paging-compose
w Pagingu 3.0 i nowszych wersjach zapewnia obsługę Compose.
Aby wyświetlić listę treści podzielonych na strony, możemy użyć funkcji rozszerzenia collectAsLazyPagingItems()
, a następnie przekazać zwrócony element LazyPagingItems
do items()
w naszym LazyColumn
. Podobnie jak w przypadku obsługi stronicowania w widokach, możesz wyświetlać symbole zastępcze podczas wczytywania danych, sprawdzając, czy item
ma wartość null
:
@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() } } } }
Wskazówki dotyczące korzystania z układów leniwych
Aby mieć pewność, że układy Lazy działają zgodnie z oczekiwaniami, możesz wziąć pod uwagę kilka wskazówek.
Unikaj elementów o rozmiarze 0 pikseli
Może się to zdarzyć w sytuacjach, w których na przykład oczekujesz asynchronicznego pobrania danych, takich jak obrazy, aby później wypełnić nimi elementy listy. Spowoduje to, że układ Lazy skomponuje wszystkie elementy w pierwszym pomiarze, ponieważ ich wysokość wynosi 0 pikseli i wszystkie zmieszczą się w widocznym obszarze. Gdy elementy zostaną wczytane i ich wysokość się zwiększy, układy Lazy odrzucą wszystkie pozostałe elementy, które zostały niepotrzebnie skomponowane za pierwszym razem, ponieważ nie mieszczą się w widocznym obszarze. Aby tego uniknąć, ustaw domyślny rozmiar produktów, aby układ Lazy mógł prawidłowo obliczyć, ile produktów zmieści się w widocznym obszarze:
@Composable fun Item(imageUrl: String) { AsyncImage( model = rememberAsyncImagePainter(model = imageUrl), modifier = Modifier.size(30.dp), contentDescription = null // ... ) }
Jeśli znasz przybliżony rozmiar elementów po asynchronicznym wczytaniu danych, dobrym rozwiązaniem jest upewnienie się, że rozmiar elementów pozostaje taki sam przed i po wczytaniu, np. przez dodanie niektórych elementów zastępczych. Pomoże to zachować prawidłową pozycję przewijania.
Unikaj zagnieżdżania komponentów, które można przewijać w tym samym kierunku
Dotyczy to tylko przypadków, gdy elementy podrzędne z możliwością przewijania bez zdefiniowanego rozmiaru są zagnieżdżone w innym elemencie nadrzędnym z możliwością przewijania w tym samym kierunku. Na przykład próba zagnieżdżenia elementu podrzędnego LazyColumn
bez stałej wysokości w element nadrzędny Column
z możliwością przewijania w pionie:
// throws IllegalStateException Column( modifier = Modifier.verticalScroll(state) ) { LazyColumn { // ... } }
Ten sam efekt można osiągnąć, umieszczając wszystkie funkcje kompozycyjne w jednym elemencie nadrzędnym LazyColumn
i używając jego DSL do przekazywania różnych typów treści. Umożliwia to emitowanie pojedynczych elementów, a także wielu elementów listy w jednym miejscu:
LazyColumn { item { Header() } items(data) { item -> PhotoItem(item) } item { Footer() } }
Pamiętaj, że dozwolone są przypadki, w których zagnieżdżasz układy o różnych kierunkach, np. przewijany element nadrzędny Row
i element podrzędny LazyColumn
:
Row( modifier = Modifier.horizontalScroll(scrollState) ) { LazyColumn { // ... } }
Oprócz przypadków, w których nadal używasz tych samych układów kierunkowych, ale ustawiasz też stały rozmiar zagnieżdżonych elementów podrzędnych:
Column( modifier = Modifier.verticalScroll(scrollState) ) { LazyColumn( modifier = Modifier.height(200.dp) ) { // ... } }
Uważaj, aby nie umieszczać wielu elementów w jednym produkcie
W tym przykładzie funkcja lambda drugiego elementu emituje 2 elementy w jednym bloku:
LazyVerticalGrid( columns = GridCells.Adaptive(100.dp) ) { item { Item(0) } item { Item(1) Item(2) } item { Item(3) } // ... }
Układy leniwe poradzą sobie z tym zgodnie z oczekiwaniami – będą układać elementy jeden po drugim, tak jakby były różnymi elementami. Istnieje jednak kilka problemów związanych z takim postępowaniem.
Gdy wiele elementów jest emitowanych w ramach jednego elementu, są one traktowane jako jedna encja, co oznacza, że nie można ich już komponować oddzielnie. Jeśli jeden element stanie się widoczny na ekranie, wszystkie elementy odpowiadające temu elementowi muszą zostać skomponowane i zmierzone. Jeśli jest nadmiernie używana, może obniżyć wydajność. W ekstremalnym przypadku umieszczenia wszystkich elementów w jednym elemencie całkowicie niweczy to cel używania układów leniwych. Oprócz potencjalnych problemów z wydajnością umieszczenie większej liczby elementów w jednym produkcie będzie też zakłócać działanie scrollToItem()
i animateScrollToItem()
.
Istnieją jednak uzasadnione przypadki umieszczania wielu elementów w jednym elemencie, np. separatorów na liście. Nie chcesz, aby separatory zmieniały indeksy przewijania, ponieważ nie powinny być traktowane jako niezależne elementy. Nie wpłynie to też na wydajność, ponieważ separatory są małe. Separator prawdopodobnie będzie musiał być widoczny, gdy widoczny jest element poprzedzający, więc może być częścią poprzedniego elementu:
LazyVerticalGrid( columns = GridCells.Adaptive(100.dp) ) { item { Item(0) } item { Item(1) Divider() } item { Item(2) } // ... }
Rozważ użycie niestandardowych układów
Zwykle listy leniwe zawierają wiele elementów i zajmują więcej miejsca niż kontener przewijania. Jeśli jednak lista zawiera niewiele elementów, projekt może mieć bardziej szczegółowe wymagania dotyczące ich rozmieszczenia w obszarze wyświetlania.
Aby to osiągnąć, możesz użyć niestandardowej branży Arrangement
i przekazać ją do parametru LazyColumn
. W poniższym przykładzie obiekt TopWithFooter
musi zaimplementować tylko metodę arrange
. Po pierwsze, elementy będą umieszczane jeden po drugim. Po drugie, jeśli łączna wykorzystana wysokość jest mniejsza niż wysokość widocznego obszaru, stopka zostanie umieszczona u dołu:
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() } } }
Rozważ dodanie contentType
Aby zmaksymalizować wydajność układu Lazy, od wersji Compose 1.2 warto dodać contentType
do list lub siatek. Dzięki temu możesz określić typ treści dla każdego elementu układu, jeśli tworzysz listę lub siatkę składającą się z wielu różnych typów elementów:
LazyColumn { items(elements, contentType = { it.type }) { // ... } }
Gdy podasz contentType
, Compose może ponownie wykorzystywać kompozycje tylko między elementami tego samego typu. Ponowne używanie jest bardziej wydajne, gdy tworzysz elementy o podobnej strukturze. Podanie typów treści gwarantuje, że funkcja Compose nie będzie próbować tworzyć elementu typu A na zupełnie innym elemencie typu B. Pomaga to maksymalizować korzyści z ponownego używania kompozycji i wydajność układu Lazy.
Pomiar skuteczności
Skuteczność układu Lazy można wiarygodnie mierzyć tylko wtedy, gdy jest on uruchomiony w trybie wydania i z włączoną optymalizacją R8. W wersjach debugowania przewijanie układu Lazy Layout może być wolniejsze. Więcej informacji znajdziesz w artykule Skuteczność kompozycji.
Dodatkowe materiały
- Tworzenie listy z nieskończonym przewijaniem
- Tworzenie przewijanej siatki
- Wyświetlanie zagnieżdżonych elementów przewijanych na liście
- Filtrowanie listy podczas pisania
- Lenistwo wczytywanie danych za pomocą list i stronicowania
- Tworzenie listy z użyciem wielu typów elementów
- Film: listy w komponowaniu
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy JavaScript jest wyłączony.
- Przenoszenie
RecyclerView
na listę Lazy - Zapisywanie stanu interfejsu w Compose
- Kotlin w Jetpack Compose