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 wyświetlać zawartość każdego elementu, iterując po liście w ten sposób:
@Composable fun MessageList(messages: List<Message>) { Column { messages.forEach { message -> MessageRow(message) } } }
Możemy sprawić, że element Column będzie można przewijać, używając modyfikatora verticalScroll().
Listy Lazy
Jeśli chcesz wyświetlić dużą liczbę elementów (lub listę o nieznanej długości), użycie układu takiego jak Column może spowodować problemy z wydajnością, ponieważ wszystkie elementy zostaną utworzone i rozmieszczone niezależnie od tego, czy są widoczne.
Compose udostępnia zestaw komponentów, które tworzą i rozmieszczają tylko elementy widoczne w widocznym obszarze komponentu. Te komponenty to LazyColumn i LazyRow.
Jak sugeruje nazwa, różnica między LazyColumn i LazyRow polega na orientacji, w której układają elementy i przewijają. 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 bloku treści @Composable, który umożliwia aplikacjom bezpośrednie emitowanie funkcji kompozycyjnych, komponenty Lazy udostępniają blok LazyListScope.(). Ten blok LazyListScope zawiera DSL, który umożliwia aplikacjom opisywanie zawartości elementu. Komponent Lazy odpowiada za dodawanie zawartości każdego elementu zgodnie z wymaganiami układu i pozycji przewijania.
LazyListScope DSL
DSL LazyListScope udostępnia szereg funkcji do opisywania elementów w układzie. W najprostszym przypadku item() dodaje pojedynczy element, a items(Int) – 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") } }
Dostępnych jest też wiele funkcji rozszerzeń, które umożliwiają dodawanie kolekcji elementów, takich jak List. Te rozszerzenia pozwalają nam łatwo przenieść nasz przykład Column z powyższego fragmentu:
/** * import androidx.compose.foundation.lazy.items */ LazyColumn { items(messages) { message -> MessageRow(message) } }
Istnieje też wariant funkcji rozszerzenia items() o nazwie itemsIndexed(), która udostępnia indeks. Więcej informacji znajdziesz w dokumentacji LazyListScope.
Leniwe siatki
Komponenty LazyVerticalGrid i LazyHorizontalGrid obsługują wyświetlanie elementów w siatce. Siatka LazyVerticalGrid wyświetla elementy w kontenerze, który można przewijać w pionie, i obejmuje wiele kolumn. Siatka LazyHorizontalGrid działa tak samo, ale w osi poziomej.
Siatki mają te same zaawansowane możliwości interfejsu API co listy i używają 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ą formowane w kolumny lub wiersze. W poniższym 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 pozwala określić szerokość elementów, a następnie siatka dopasuje jak najwięcej kolumn. Pozostała szerokość jest rozdzielana równomiernie 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óre mają być użyte, 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 określić niestandardowe rozpiętości kolumn dla elementów.
Określ rozpiętość kolumn za pomocą parametru span metod LazyGridScope DSL
item i items.
maxLineSpan, jedna z wartości zakresu rozpiętości, jest szczególnie przydatna, gdy używasz rozmiaru adaptacyjnego, ponieważ liczba kolumn nie jest stała.
Ten przykład pokazuje, jak ustawić pełną szerokość wiersza:
LazyVerticalGrid( columns = GridCells.Adaptive(minSize = 30.dp) ) { item(span = { // LazyGridItemSpanScope: // maxLineSpan GridItemSpan(maxLineSpan) }) { CategoryCard("Fruits") } // ... }
Leniwa siatka z przesunięciem
LazyVerticalStaggeredGrid i LazyHorizontalStaggeredGrid to elementy kompozycyjne, które umożliwiają utworzenie leniwie ładowanej, przesuniętej siatki elementów.
Leniwa pionowa siatka z przesunięciem wyświetla elementy w kontenerze z możliwością przewijania w pionie, który obejmuje wiele kolumn i umożliwia stosowanie różnych wysokości poszczególnych elementów. Leniwe siatki poziome zachowują się 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 szerokością 200.dp 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 przypisuje każdemu elementowi 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() )
Dopełnienie treści
Czasami trzeba dodać dopełnienie wokół krawędzi treści. Komponenty Lazy umożliwiają przekazywanie niektórych PaddingValues do parametru contentPadding, aby to obsługiwać:
LazyColumn( contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp), ) { // ... }
W tym przykładzie dodajemy dopełnienie 16.dp do krawędzi poziomych (lewej i prawej) oraz 8.dp do górnej i dolnej krawędzi treści.
Pamiętaj, że dopełnienie jest stosowane do treści, a nie do samego elementu LazyColumn. W przykładzie powyżej pierwszy element będzie miał dopełnienie 8.dp u góry, ostatni – dopełnienie 8.dp u dołu, a wszystkie elementy będą miały dopełnienie 16.dp po lewej i prawej stronie.
Inny przykład: możesz przekazać PaddingValues z Scaffold do contentPadding w LazyColumn. 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ć Arrangement.spacedBy().
W przykładzie poniżej dodano 4.dp odstępu między elementami:
LazyColumn( verticalArrangement = Arrangement.spacedBy(4.dp), ) { // ... }
Podobnie w przypadku LazyRow:
LazyRow( horizontalArrangement = Arrangement.spacedBy(4.dp), ) { // ... }
Siatki mogą być jednak układane zarówno w pionie, jak i w poziomie:
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 pozycją elementu na liście lub w siatce. Może to jednak powodować problemy, jeśli zbiór danych się zmieni, ponieważ elementy, które zmienią pozycję, utracą zapamiętany stan. Wyobraź sobie sytuację, w której LazyRow znajduje się w LazyColumn. Jeśli wiersz zmieni pozycję elementu, użytkownik utraci pozycję przewijania w wierszu.
Aby temu zapobiec, możesz podać stabilny i unikalny klucz dla każdego elementu, podając blok parametrowi 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) } }
Podając klucze, pomagasz Compose prawidłowo obsługiwać zmiany kolejności. Jeśli na przykład element zawiera zapamiętany stan, ustawienie kluczy pozwoli Compose przenieść ten stan razem 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 elementów.
Typ klucza musi być obsługiwany przez Bundle, czyli mechanizm Androida do zachowywania stanów podczas ponownego tworzenia aktywności. Bundle obsługuje typy takie jak typy podstawowe,
typy wyliczeniowe czy 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 elemencie kompozycyjnym, gdy aktywność zostanie ponownie utworzona, a nawet gdy przewiniesz ekran 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 automatycznie animuje on zmiany elementów.
Leniwe układy zapewniają tę samą funkcjonalność w przypadku zmiany kolejności elementów.
Interfejs API jest prosty – wystarczy ustawić modyfikator animateItem dla zawartości elementu:
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) ) ) { // ... } } }
Upewnij się, że podajesz klucze do elementów, aby można było znaleźć nową pozycję przeniesionego elementu.
Przykład: animowanie elementów na listach z opóźnionym ładowaniem
W Compose możesz animować zmiany elementów na listach leniwych. Gdy użyjesz ich razem, te fragmenty kodu będą implementować animacje podczas dodawania, usuwania i zmieniania 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
ListAnimatedItemswyświetla listę ciągów znaków wLazyColumnz animowanymi przejściami, gdy elementy są modyfikowane.- Funkcja
itemsprzypisuje unikalny klucz do każdego elementu na liście. Compose używa kluczy do śledzenia elementów i identyfikowania zmian w ich pozycjach. ListItemdefiniuje układ każdego elementu listy. Przyjmuje parametrheadlineContent, który definiuje główną treść elementu.- Modyfikator
animateItemstosuje domyślne animacje do dodawania, usuwania i przenoszenia elementów.
Poniższy fragment kodu przedstawia ekran, który zawiera elementy sterujące do dodawania i usuwania elementów oraz sortowania wstępnie zdefiniowanej 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
ListAnimatedItemsExamplewyświetla ekran z elementami sterującymi do dodawania, usuwania i sortowania elementów.onAddItemionRemoveItemto wyrażenia lambda przekazywane doAddRemoveButtonsw celu dodawania i usuwania elementów z listy.resetOrder,onSortAlphabeticallyionSortByLengthto wyrażenia lambda przekazywane doOrderButtonsw celu zmiany kolejności elementów na liście.
AddRemoveButtonswyświetla przyciski „Dodaj” i „Usuń”. Włącza/wyłącza przyciski i obsługuje kliknięcia przycisków.OrderButtonswyś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.ListAnimatedItemswywołuje element kompozycyjnyListAnimatedItems, przekazując listędata, aby wyświetlić animowaną listę ciągów tekstowych.datajest zdefiniowany 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
AddRemoveButtonswyświetla wiersz przycisków umożliwiających dodawanie i usuwanie elementów listy.- Parametry
canAddItemicanRemoveItemokreślają stan włączenia przycisków. Jeśli zasadacanAddItemlubcanRemoveItemma wartość Fałsz, odpowiedni przycisk jest wyłączony. - Parametry
onAddItemionRemoveItemto 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
OrderButtonswyświetlaSingleChoiceSegmentedButtonRow, aby umożliwić użytkownikom wybranie metody sortowania na liście lub zresetowanie kolejności listy. KomponentSegmentedButtonumożliwia wybranie jednej opcji z listy.resetOrder,orderAlphabeticallyiorderByLengthto funkcje lambda, które są wykonywane po wybraniu odpowiedniego przycisku.- Zmienna stanu
selectedIndexśledzi wybraną opcję.
Wynik
Ten film pokazuje wynik działania poprzednich fragmentów kodu, gdy elementy są zmieniane:
Przyklejone nagłówki
Wzorzec „przyklejonego nagłówka” jest przydatny podczas wyświetlania list pogrupowanych danych. Poniżej znajdziesz przykład listy kontaktów pogrupowanej według pierwszej litery nazwy kontaktu:

Aby uzyskać przyklejony nagłówek w LazyColumn, możesz użyć stickyHeader().
, która dostarcza treść nagłówka:
@Composable fun ListWithHeader(items: List<Item>) { LazyColumn { stickyHeader { Header() } items(items) { item -> ItemRow(item) } } }
Aby utworzyć listę z wieloma nagłówkami, 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] } @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 oraz je śledzić.
Komponenty Lazy obsługują ten przypadek użycia przez podniesienie LazyListState:
@Composable fun MessageList(messages: List<Message>) { // Remember our own LazyListState val listState = rememberLazyListState() // Provide it to LazyColumn LazyColumn(state = listState) { // ... } }
W prostych przypadkach aplikacje zwykle potrzebują tylko informacji o pierwszym widocznym elemencie. W tym celu LazyListState udostępnia właściwości firstVisibleItemIndex i firstVisibleItemScrollOffset.
Jeśli weźmiemy przykład wyświetlania i ukrywania przycisku w zależności od tego, czy użytkownik przewinął 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 elementy kompozycyjne interfejsu, ale są też scenariusze, w których zdarzenie nie musi być obsługiwane w tej samej kompozycji. Typowym przykładem jest wysyłanie zdarzenia analitycznego, gdy użytkownik przewinie stronę poza określony punkt. 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 elementach, które są obecnie wyświetlane, oraz o ich granicach na ekranie za pomocą właściwości layoutInfo. Więcej informacji znajdziesz w klasie LazyListLayoutInfo.
Kontrolowanie pozycji przewijania
Oprócz reagowania na pozycję przewijania aplikacje mogą też kontrolować pozycję przewijania.
LazyListState obsługuje tę funkcję za pomocą funkcji scrollToItem(), która „natychmiast” przyciąga pozycję przewijania, oraz animateScrollToItem(), która przewija za pomocą animacji (nazywanej też płynnym przewijaniem):
@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 stronicowania umożliwia aplikacjom obsługę dużych list elementów, wczytywanie i wyświetlanie niewielkich fragmentów listy w razie potrzeby. Paging 3.0 i nowsze wersje obsługują Compose dzięki bibliotece androidx.paging:paging-compose.
Aby wyświetlić listę treści podzielonych na strony, możemy użyć funkcji rozszerzenia collectAsLazyPagingItems(), a następnie przekazać zwrócony obiekt LazyPagingItems do items() w LazyColumn. Podobnie jak w przypadku obsługi stronicowania w widokach, możesz wyświetlać obiekty zastępcze podczas wczytywania danych, sprawdzając, czy item to 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 Lazy
Aby układy Lazy działały zgodnie z oczekiwaniami, możesz skorzystać z kilku wskazówek.
Unikaj używania elementów o rozmiarze 0 pikseli.
Może się tak zdarzyć na przykład wtedy, gdy chcesz asynchronicznie pobrać dane, takie jak obrazy, aby później wypełnić elementy listy. Spowodowałoby to, że układ Lazy utworzyłby wszystkie elementy w pierwszym pomiarze, ponieważ ich wysokość wynosi 0 pikseli i wszystkie zmieściłyby się w widocznym obszarze. Po załadowaniu elementów i zwiększeniu ich wysokości układy Lazy odrzucą wszystkie pozostałe elementy, które zostały niepotrzebnie utworzone za pierwszym razem, ponieważ nie mieszczą się w obszarze widocznym. Aby tego uniknąć, ustaw domyślny rozmiar elementów, dzięki czemu układ Lazy będzie mógł prawidłowo obliczyć, ile elementów może się zmieścić 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 załadowaniu danych, dobrą praktyką jest upewnienie się, że rozmiar elementów pozostaje taki sam przed załadowaniem i po nim, na przykład przez dodanie obiektó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, które można przewijać, bez wstępnie zdefiniowanego rozmiaru są zagnieżdżone w innym elemencie nadrzędnym, który można przewijać w tym samym kierunku. Na przykład próba zagnieżdżenia elementu podrzędnego LazyColumn bez stałej wysokości w elemencie nadrzędnym Column, który można przewijać w pionie:
// throws IllegalStateException Column( modifier = Modifier.verticalScroll(state) ) { LazyColumn { // ... } }
Zamiast tego ten sam efekt można osiągnąć, umieszczając wszystkie elementy kompozycyjne w jednym elemencie nadrzędnym LazyColumn i używając jego DSL do przekazywania różnych typów treści. Dzięki temu możesz emitować pojedyncze elementy, a także wiele elementów listy, w jednym miejscu:
LazyColumn { item { Header() } items(data) { item -> PhotoItem(item) } item { Footer() } }
Pamiętaj, że przypadki, w których zagnieżdżasz układy o różnych kierunkach, na przykład przewijany element nadrzędny Row i element podrzędny LazyColumn, są dozwolone:
Row( modifier = Modifier.horizontalScroll(scrollState) ) { LazyColumn { // ... } }
Możesz też używać tych samych układów kierunku, ale ustawić stały rozmiar zagnieżdżonych elementów podrzędnych:
Column( modifier = Modifier.verticalScroll(scrollState) ) { LazyColumn( modifier = Modifier.height(200.dp) ) { // ... } }
Unikaj umieszczania wielu elementów w jednym elemencie
W tym przykładzie druga funkcja lambda 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 Lazy będą obsługiwać to zgodnie z oczekiwaniami – będą układać elementy jeden po drugim, tak jakby były to różne elementy. Jednak takie rozwiązanie ma kilka wad.
Gdy wiele elementów jest emitowanych jako część jednego elementu, są one traktowane jako jedna encja, co oznacza, że nie można ich już komponować pojedynczo. Jeśli jeden element stanie się widoczny na ekranie, wszystkie elementy odpowiadające temu elementowi muszą zostać skomponowane i zmierzone. Jeśli będziesz używać ich zbyt często, może to negatywnie wpłynąć na skuteczność. W skrajnym przypadku, gdy wszystkie elementy zostaną umieszczone w jednym elemencie, całkowicie mija się to z celem korzystania z układów Lazy. Oprócz potencjalnych problemów z wydajnością umieszczenie większej liczby elementów w jednym elemencie będzie też zakłócać działanie scrollToItem() i animateScrollToItem().
Istnieją jednak uzasadnione przypadki użycia, w których można umieścić wiele elementów w jednym elemencie, np. separatory na liście. Nie chcesz, aby separatory zmieniały indeksy przewijania, ponieważ nie powinny być traktowane jako niezależne elementy. Nie ma to też wpływu na wydajność, ponieważ separatory są małe. Separator powinien być widoczny, gdy widoczny jest element przed nim, 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 aranżacji
Zwykle listy Lazy zawierają wiele elementów i zajmują więcej miejsca niż rozmiar kontenera przewijania. Jeśli jednak lista zawiera niewiele elementów, projekt może mieć bardziej szczegółowe wymagania dotyczące ich rozmieszczenia w widocznym obszarze.
Aby to zrobić, możesz użyć niestandardowej branży Arrangement i przekazać ją do LazyColumn. W poniższym przykładzie obiekt TopWithFooter musi implementować 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 na dole:
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
Począwszy od Compose 1.2, aby zmaksymalizować wydajność układu Lazy, rozważ dodanie do list lub siatek elementu contentType. Dzięki temu możesz określić typ treści każdego elementu układu, gdy 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 używać kompozycji tylko między elementami tego samego typu. Ponieważ ponowne użycie jest bardziej efektywne, gdy tworzysz elementy o podobnej strukturze, podanie typów treści gwarantuje, że Compose nie spróbuje utworzyć elementu typu A na podstawie zupełnie innego elementu typu B. Pomaga to zmaksymalizować korzyści płynące z ponownego użycia kompozycji i wydajności układu Lazy.
Pomiar skuteczności
Wydajność układu Lazy można wiarygodnie zmierzyć tylko w trybie wersji i przy włączonej optymalizacji R8. W kompilacjach do debugowania przewijanie układu Lazy może wydawać się wolniejsze. Więcej informacji znajdziesz w artykule o wydajności Compose.
Dodatkowe materiały
- Tworzenie ograniczonej listy przewijanej
- Tworzenie siatki z możliwością przewijania
- Wyświetlanie zagnieżdżonych elementów przewijanych na liście
- Filtrowanie listy podczas pisania
- Leniwe ładowanie danych za pomocą list i stronicowania
- Tworzenie listy z użyciem wielu typów produktów
- Film: Listy w Compose
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy język JavaScript jest wyłączony.
- Przenoszenie elementu
RecyclerViewdo listy Lazy - Zapisywanie stanu interfejsu w Compose
- Kotlin w Jetpack Compose