Listy i siatki

Wiele aplikacji musi wyświetlać kolekcje elementów. Ten dokument wyjaśnia, jak to zrobić w Jetpack Compose.

Jeśli wiesz, że w Twoim przypadku nie potrzeba przewijania, możesz użyć prostego Column lub Row (w zależności od kierunku) i wyświetlać 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 zwiększyć zakres przewijania Column dzięki modyfikatorowi verticalScroll(). Więcej informacji znajdziesz w dokumentacji gestów.

Leniwe listy

Jeśli musisz wyświetlać dużą liczbę elementów (lub listę o nieznanej długości), używanie układu takiego jak Column może powodować problemy z wydajnością, ponieważ wszystkie elementy zostaną utworzone i określone, czy będą widoczne.

Tworzenie obejmuje zestaw komponentów, które tworzą i rozkładają tylko elementy widoczne w widocznym obszarze komponentu. Te komponenty to m.in. LazyColumn i LazyRow.

Jak sama nazwa wskazuje, metodą LazyColumn i LazyRow jest orientacja, w której rozmieszczają elementy i przewijają. LazyColumn tworzy listę przewijaną w pionie, a LazyRow – listę przewijaną w poziomie.

Komponenty Leniwego różnią się od większości układów w obszarze tworzenia wiadomości. Zamiast akceptowania parametru blokowania treści @Composable, który pozwala aplikacjom bezpośrednio emitować elementy kompozycyjne, komponenty Lazy zawierają blok LazyListScope.(). Ten blok LazyListScope udostępnia DSL, który umożliwia aplikacjom opisanie zawartości elementu. Następnie komponent leniwy odpowiada za dodanie treści każdego elementu zgodnie z wymaganiami dotyczącymi układu i pozycji przewijania.

LazyListScope LDL

DSL interfejsu LazyListScope udostępnia wiele funkcji do opisywania elementów w układzie. Najprostsza metoda item() dodaje 1 element, a items(Int) – wiele:

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, np. List. Te rozszerzenia pozwalają nam łatwo przenieść opisany wyżej przykład Column:

/**
 * import androidx.compose.foundation.lazy.items
 */
LazyColumn {
    items(messages) { message ->
        MessageRow(message)
    }
}

Istnieje też wariant funkcji rozszerzenia items() o nazwie itemsIndexed(), która dostarcza indeks. Więcej informacji znajdziesz w dokumentacji LazyListScope.

Leniwe siatki

Komponenty kompozycyjne LazyVerticalGrid i LazyHorizontalGrid umożliwiają wyświetlanie elementów w siatce. Elementy leniwej pionowej siatki wyświetlają się w kontenerze z możliwością przewijania w pionie, rozpiętym między wieloma kolumnami, a leniwe poziome siatki działają tak samo na osi poziomej.

Siatki mają te same zaawansowane funkcje interfejsów API co listy, ale do opisywania treści wykorzystują bardzo podobną technologię DSL – LazyGridScope.().

Zrzut ekranu telefonu z siatką zdjęć

Parametry columns w LazyVerticalGrid i rows w LazyHorizontalGrid określają sposób tworzenia kolumn i wierszy z komórek. W tym przykładzie elementy są wyświetlane w siatce przy użyciu parametru GridCells.Adaptive, który pozwala ustawić 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 siatka będzie pasować do jak największej liczby kolumn. Po obliczeniu liczby kolumn pozostała szerokość jest równomiernie rozkładana między kolumny. Ten adaptacyjny sposób dostosowywania rozmiaru jest szczególnie przydatny przy wyświetlaniu zestawów produktów na ekranach o różnych rozmiarach.

Jeśli znasz dokładną liczbę kolumn do użycia, możesz zamiast tego udostępnić wystąpienie obiektu GridCells.Fixed zawierającego liczbę wymaganych kolumn.

Jeśli Twój projekt wymaga, by tylko niektóre elementy miały niestandardowe wymiary, możesz użyć obsługi siatki, by dodać niestandardowe rozpiętości kolumn dla elementów. Określ rozpiętość kolumny za pomocą parametru span metod LazyGridScope DSL item i items. maxLineSpan, jedna z wartości zakresu spanu, jest szczególnie przydatna, gdy używasz rozmiarów adaptacyjnych, ponieważ liczba kolumn nie jest stała. Ten przykład pokazuje, jak podać pełne spany wierszy:

LazyVerticalGrid(
    columns = GridCells.Adaptive(minSize = 30.dp)
) {
    item(span = {
        // LazyGridItemSpanScope:
        // maxLineSpan
        GridItemSpan(maxLineSpan)
    }) {
        CategoryCard("Fruits")
    }
    // ...
}

Leniwa siatka szeregowa

LazyVerticalStaggeredGrid i LazyHorizontalStaggeredGrid to funkcje kompozycyjne umożliwiające tworzenie leniwej, ułożonej w kolejności siatki elementów. Leniwa pionowa siatka szeregowa wyświetla elementy w przewijanym w pionie kontenerze, który rozciąga się na wiele kolumn i pozwala poszczególnym elementom o różnej wysokości. Leniwe poziome siatki działają tak samo na osi poziomej w przypadku elementów o różnej szerokości.

Ten fragment kodu to podstawowy przykład użycia atrybutu LazyVerticalStaggeredGrid o szerokości 200.dp przypadającej 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()
)

Rysunek 1. Przykład leniwej, podzielonej pionowej siatki

Aby ustawić stałą liczbę kolumn, możesz użyć parametru StaggeredGridCells.Fixed(columns) zamiast StaggeredGridCells.Adaptive. W ten sposób dostępna szerokość zostanie podzielona przez liczbę kolumn (wierszy w siatce poziomej) i każdy element będzie miał tę szerokość (czyli wysokość w przypadku poziomej siatki):

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()
)

Leniwa, rozmieszczona siatka obrazów w sekcji Utwórz
Rysunek 2. Przykład leniwej, przesuniętej pionowej siatki ze stałymi kolumnami

Dopełnienie treści

Czasami trzeba dodać dopełnienie na krawędziach treści. Leniwe komponenty umożliwiają przekazanie do parametru contentPadding części PaddingValues, aby umożliwić obsługę tego poziomu:

LazyColumn(
    contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
) {
    // ...
}

W tym przykładzie dodajemy 16.dp dopełnienia do poziomych krawędzi (lewej i prawej), a następnie 8.dp na górze i na dole treści.

Pamiętaj, że to dopełnienie jest zastosowane do treści, a nie do samej treści LazyColumn. W przykładzie powyżej pierwszy element będzie miał dopełnienie 8.dp na górze, ostatni element na dole, a wszystkie elementy będą mieć dopełnienie 16.dp z lewej i prawej strony.8.dp

Odstępy między treściami

Aby dodać odstępy między elementami, możesz użyć Arrangement.spacedBy(). W podanym niżej przykładzie między poszczególnymi elementami dodano 4.dp spacji:

LazyColumn(
    verticalArrangement = Arrangement.spacedBy(4.dp),
) {
    // ...
}

Analogicznie w przypadku LazyRow:

LazyRow(
    horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
    // ...
}

Siatki obsługują 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 zależny od pozycji elementu na liście lub siatce. Może to jednak powodować problemy w przypadku zmiany zbioru danych, ponieważ elementy, które zmieniają swoją pozycję, tracą zapamiętany stan. Jeśli wyobrazisz sobie, jak wygląda sytuacja z funkcją LazyRow w elemencie LazyColumn, to gdy wiersz zmieni pozycję elementu, użytkownik straci swoją pozycję przewijania w obrębie wiersza.

Aby z nim walczyć, możesz podać stabilny i unikalny klucz do każdego elementu, który będzie blokował parametr key. Zapewnienie stabilnego klucza sprawia, że stan elementu jest spójny 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, ułatwisz tworzenie wiadomości w celu prawidłowej obsługi zmian kolejności. Jeśli na przykład element zawiera zapamiętany stan, ustawienie kluczy pozwoli utworzyć klucz w tym stanie razem z elementem, gdy zmieni się jego pozycja.

LazyColumn {
    items(books, key = { it.id }) {
        val rememberedValue = remember {
            Random.nextInt()
        }
    }
}

Obowiązuje jednak jedno ograniczenie typów kluczy elementów, których możesz używać. Typ klucza musi być obsługiwany przez Bundle, mechanizm Androida do zachowywania stanów podczas odtwarzania aktywności. Bundle obsługuje takie typy elementów jak podstawowe, wyliczenia i elementy Parcelable.

LazyColumn {
    items(books, key = {
        // primitives, enums, Parcelable, etc.
    }) {
        // ...
    }
}

Klucz musi być obsługiwany w systemie Bundle, aby można było przywrócić rememberSaveable wewnątrz elementu kompozycyjnego, gdy aktywność zostanie odtworzona, a nawet gdy przewiniesz stronę w dół i przewiniesz stronę do tyłu.

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 produktów. Leniwe układy mają tę samą funkcję w przypadku zmiany kolejności elementów. Interfejs API jest prosty – wystarczy ustawić modyfikator animateItemPlacement na zawartość elementu:

LazyColumn {
    items(books, key = { it.id }) {
        Row(Modifier.animateItemPlacement()) {
            // ...
        }
    }
}

Możesz nawet udostępnić niestandardową specyfikację animacji, jeśli chcesz:

LazyColumn {
    items(books, key = { it.id }) {
        Row(
            Modifier.animateItemPlacement(
                tween(durationMillis = 250)
            )
        ) {
            // ...
        }
    }
}

Pamiętaj, by podać klucze do elementów, aby można było znaleźć nową pozycję przenoszonego elementu.

Poza zmianą kolejności pracujemy obecnie nad animacjami dodawania i usuwania elementów. Postępy możesz sprawdzić w numerze 150812265.

Przyklejone nagłówki (funkcja eksperymentalna)

Wzorzec „przyklejony nagłówek” jest przydatny przy wyświetlaniu list pogrupowanych danych. Poniżej znajdziesz przykład „listy kontaktów”, pogrupowanej według inicjałów każdego kontaktu:

Film przedstawiający telefon przewijający listę kontaktów w górę i w dół

Aby uzyskać przyklejony nagłówek za pomocą funkcji LazyColumn, możesz użyć eksperymentalnej funkcji stickyHeader(), podając treść nagłówka:

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun ListWithHeader(items: List<Item>) {
    LazyColumn {
        stickyHeader {
            Header()
        }

        items(items) { item ->
            ItemRow(item)
        }
    }
}

Aby utworzyć listę z wieloma nagłówkami, jak w powyższym przykładzie z listą kontaktów, możesz:

// 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)
            }
        }
    }
}

Reakcja na pozycję przewijania

Wiele aplikacji musi reagować na zmiany pozycji przewijania i układu elementów oraz reagować na nie. Komponenty Lazy obsługują ten przypadek użycia przez podniesienie obiektu 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ą jedynie informacji o pierwszym widocznym elemencie. W tym przypadku LazyListState podaje właściwości firstVisibleItemIndex i firstVisibleItemScrollOffset.

Jeśli użyjemy przykładu pokazywania i ukrywania przycisku na podstawie tego, czy użytkownik przewinął pierwszy element:

@OptIn(ExperimentalAnimationApi::class)
@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 ramach kompozycji jest przydatne, gdy musisz zaktualizować inne funkcje kompozycyjne interfejsu. Są jednak również sytuacje, w których zdarzenie nie musi być obsługiwane w tej samej kompozycja. Typowym przykładem takiej sytuacji jest wysyłanie przez użytkownika zdarzenia Analytics, gdy użytkownik przewinie widok poza określony punkt. Aby skutecznie to zrobić, 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()
        }
}

Za pomocą właściwości layoutInfo LazyListState podaje też informacje o wszystkich aktualnie wyświetlanych elementach oraz o ich granicach na ekranie. Więcej informacji znajdziesz w klasie LazyListLayoutInfo.

Kontrolowanie pozycji przewijania

Oprócz reagowania na pozycję przewijania aplikacje mogą też decydować o położeniu przewijania. LazyListState obsługuje to za pomocą funkcji scrollToItem(), która „natychmiast” przyciąga pozycję przewijania oraz animateScrollToItem(), która przewija się przy użyciu 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 stron docelowych umożliwia aplikacjom obsługę dużych list elementów oraz wczytywanie i wyświetlanie ich niewielkich fragmentów. Strona Stron w wersji 3.0 i nowszych umożliwia tworzenie wiadomości za pomocą biblioteki androidx.paging:paging-compose.

Aby wyświetlić listę treści podzielonych na strony, możemy użyć funkcji rozszerzenia collectAsLazyPagingItems(), a potem przekazać zwrócone wartości LazyPagingItems do items() w LazyColumn. Podobnie jak w przypadku obsługi stron w widokach, podczas wczytywania danych możesz wyświetlać zmienne, 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 leniwych układów

Oto kilka wskazówek, które warto wziąć pod uwagę, aby mieć pewność, że Twoje leniwe układy będą działać zgodnie z oczekiwaniami.

Unikaj używania elementów o rozmiarze 0 pikseli.

Może się to zdarzyć w sytuacjach, gdy np. chcesz asynchronicznie pobierać niektóre dane, takie jak obrazy, aby na późniejszym etapie wypełnić elementy listy. Spowodowałoby to złożenie wszystkich elementów w układzie leniwego w pierwszym pomiarze, ponieważ ich wysokość wynosi 0 pikseli i mogłyby zmieścić się w widocznym obszarze. Po wczytaniu elementów i rozszerzeniu ich wysokości leniwe układy odrzucają wtedy wszystkie pozostałe elementy, które niepotrzebnie zostały utworzone, ponieważ w rzeczywistości nie mieszczą się w widocznym obszarze. Aby tego uniknąć, ustaw domyślne rozmiary elementów, tak by układ leniwy mógł właściwie obliczyć, ile z nich 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 wczytaniu danych asynchronicznie, zadbaj o to, by rozmiar tych elementów nie zmieniał się przed załadowaniem i po jego wczytaniu, na przykład dodając kilka obiektów zastępczych. Pomoże to utrzymać prawidłową pozycję przewijania.

Unikaj zagnieżdżania komponentów, które można przewijać w tym samym kierunku

Dotyczy to tylko zagnieżdżania elementów podrzędnych z możliwością przewijania bez wstępnie zdefiniowanego rozmiaru wewnątrz innego elementu nadrzędnego z możliwością przewijania. Przykład: próba zagnieżdżenia podrzędnego elementu LazyColumn bez stałej wysokości w elemencie nadrzędnym Column z możliwością przewijania w pionie:

// throws IllegalStateException
Column(
    modifier = Modifier.verticalScroll(state)
) {
    LazyColumn {
        // ...
    }
}

Ten sam efekt można osiągnąć przez umieszczenie wszystkich funkcji kompozycyjnych wewnątrz jednego elementu nadrzędnego LazyColumn i użycie DSL do przekazywania różnych typów treści. Umożliwia to wysyłanie zarówno pojedynczych elementów, jak i wielu elementów listy w jednym miejscu:

LazyColumn {
    item {
        Header()
    }
    items(data) { item ->
        PhotoItem(item)
    }
    item {
        Footer()
    }
}

Pamiętaj, że gdy zagnieżdżasz różne układy kierunków, na przykład element nadrzędny Row z możliwością przewijania i element podrzędny LazyColumn, są dozwolone:

Row(
    modifier = Modifier.horizontalScroll(scrollState)
) {
    LazyColumn {
        // ...
    }
}

Także w przypadkach, w których nadal używasz tych samych układów kierunku, ale ustawiasz stały rozmiar dla zagnieżdżonych elementów podrzędnych:

Column(
    modifier = Modifier.verticalScroll(scrollState)
) {
    LazyColumn(
        modifier = Modifier.height(200.dp)
    ) {
        // ...
    }
}

Uważaj na umieszczenie wielu elementów w jednym elemencie

W tym przykładzie drugi element lambda emituje 2 elementy w jednym bloku:

LazyVerticalGrid(
    columns = GridCells.Adaptive(100.dp)
) {
    item { Item(0) }
    item {
        Item(1)
        Item(2)
    }
    item { Item(3) }
    // ...
}

Leniwe układy poradziły sobie z tym zgodnie z oczekiwaniami – rozmieszczają elementy jeden po drugim, jakby były to różne elementy. Istnieją jednak pewne problemy.

Gdy w ramach 1 elementu jest wysyłanych wiele elementów, są one traktowane jako jeden element, co oznacza, że nie można już tworzyć ich oddzielnie. Jeśli 1 element staje się widoczny na ekranie, należy utworzyć i zmierzyć wszystkie odpowiadające mu elementy. Nadmierne użycie może negatywnie wpłynąć na wydajność. W skrajnym przypadku umieszczenie wszystkich elementów w jednym elemencie całkowicie nie pozwala na użycie leniwego układu. Oprócz potencjalnych problemów z wydajnością umieszczenie większej liczby elementów w jednym elemencie wpłynie też na działanie scrollToItem() i animateScrollToItem().

Istnieją jednak sposoby umieszczania wielu elementów w jednym elemencie, np. separatory w liście. Nie chcemy, by separatory zmieniały indeksy przewijania, ponieważ nie powinny one być uznawane za elementy niezależne. Nie wpłynie to też na skuteczność, bo separatory są małe. Separator zwykle musi być widoczny w momencie, gdy element jest widoczny, tak aby mógł być częścią poprzedniego elementu:

LazyVerticalGrid(
    columns = GridCells.Adaptive(100.dp)
) {
    item { Item(0) }
    item {
        Item(1)
        Divider()
    }
    item { Item(2) }
    // ...
}

Zastanów się nad dostosowaniem układu

Zazwyczaj leniwe listy zawierają wiele elementów i zajmują więcej miejsca niż rozmiar przewijanego kontenera. Jeśli jednak lista zawiera niewielką liczbę elementów, Twój projekt może mieć bardziej szczegółowe wymagania dotyczące ich umiejscowienia w widocznym obszarze.

Aby to osiągnąć, możesz użyć niestandardowej branży Arrangement i przekazać ją do LazyColumn. W poniższym przykładzie obiekt TopWithFooter musi zaimplementować tylko metodę arrange. Po pierwsze, elementy umieszcza się jeden po drugim. Po drugie, jeśli łączna używana 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 funkcji Utwórz 1.2, aby zmaksymalizować wydajność układu Leniwego, możesz zacząć dodawać do list lub siatek parametr contentType. Dzięki temu możesz określić typ treści dla każdego elementu układu, gdy tworzysz listę lub siatkę złożoną z wielu różnych typów elementów:

LazyColumn {
    items(elements, contentType = { it.type }) {
        // ...
    }
}

Gdy podasz contentType, funkcja tworzenia treści będzie mogła ponownie wykorzystywać kompozycje tylko między elementami tego samego typu. Ponowne wykorzystanie jest wydajniejsze, gdy tworzysz elementy o podobnej strukturze, dlatego podanie typów treści pozwoli uniknąć próby utworzenia elementu typu A na podstawie zupełnie innego elementu typu B. Pomaga to w maksymalnym wykorzystaniu ponownego użycia kompozycji i skuteczności leniwego układu.

Pomiar skuteczności

Możesz miarodajnie mierzyć wydajność leniwego układu tylko wtedy, gdy działa on w trybie wydania i ma włączoną optymalizację R8. W kompilacjach do debugowania przewijanie przez leniwy układ może działać wolniej. Więcej informacji na ten temat znajdziesz w artykule Wydajność tworzenia wiadomości.