Многим приложениям необходимо отображать коллекции элементов. В этом документе объясняется, как можно эффективно сделать это в Jetpack Compose.
Если вы знаете, что ваш вариант использования не требует прокрутки, вы можете использовать простой Column
или Row
(в зависимости от направления) и выводить содержимое каждого элемента путем итерации по списку следующим образом:
@Composable fun MessageList(messages: List<Message>) { Column { messages.forEach { message -> MessageRow(message) } } }
Мы можем сделать Column
прокручиваемым, используя модификатор verticalScroll()
.
Ленивые списки
Если вам необходимо отобразить большое количество элементов (или список неизвестной длины), использование такого макета, как Column
, может вызвать проблемы с производительностью, поскольку все элементы будут скомпонованы и размещены независимо от того, видны они или нет.
Compose предоставляет набор компонентов, которые только компонуют и располагают элементы, видимые в области просмотра компонента. Эти компоненты включают LazyColumn
и LazyRow
.
Как следует из названия, разница между LazyColumn
и LazyRow
заключается в ориентации, в которой они располагают свои элементы и прокручивают. LazyColumn
создает вертикально прокручиваемый список, а LazyRow
создает горизонтально прокручиваемый список.
Компоненты Lazy отличаются от большинства макетов в Compose. Вместо того, чтобы принимать параметр блока контента @Composable
, позволяющий приложениям напрямую выдавать компонуемые элементы, компоненты Lazy предоставляют блок LazyListScope.()
. Этот блок LazyListScope
предлагает DSL, позволяющий приложениям описывать содержимое элементов. Затем компонент Lazy отвечает за добавление содержимого каждого элемента в соответствии с требованиями макета и положения прокрутки.
DSL-модуль LazyListScope
DSL LazyListScope
предоставляет ряд функций для описания элементов в макете. В самом простом случае item()
добавляет один элемент, а items(Int)
добавляет несколько элементов:
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") } }
Также есть ряд функций расширения, которые позволяют добавлять коллекции элементов, такие как List
. Эти расширения позволяют нам легко перенести наш пример Column
из вышеприведенного примера:
/** * import androidx.compose.foundation.lazy.items */ LazyColumn { items(messages) { message -> MessageRow(message) } }
Существует также вариант функции расширения items()
под названием itemsIndexed()
, который предоставляет индекс. Пожалуйста, смотрите ссылку LazyListScope
для получения более подробной информации.
Ленивые сетки
Компоновочные элементы LazyVerticalGrid
и LazyHorizontalGrid
обеспечивают поддержку отображения элементов в сетке. Вертикальная сетка Lazy будет отображать свои элементы в вертикально прокручиваемом контейнере, охватывающем несколько столбцов, в то время как горизонтальные сетки Lazy будут иметь такое же поведение на горизонтальной оси.
Сетки обладают такими же мощными возможностями API, как и списки, а также используют очень похожий DSL — LazyGridScope.()
для описания содержимого.
Параметр columns
в LazyVerticalGrid
и параметр rows
в LazyHorizontalGrid
управляют тем, как ячейки формируются в столбцы или строки. Следующий пример отображает элементы в сетке, используя GridCells.Adaptive
для установки каждого столбца шириной не менее 128.dp
:
LazyVerticalGrid( columns = GridCells.Adaptive(minSize = 128.dp) ) { items(photos) { photo -> PhotoItem(photo) } }
LazyVerticalGrid
позволяет вам указать ширину для элементов, и тогда сетка вместит столько столбцов, сколько возможно. Любая оставшаяся ширина распределяется поровну между столбцами после расчета количества столбцов. Этот адаптивный способ изменения размера особенно полезен для отображения наборов элементов на экранах разных размеров.
Если вы знаете точное количество используемых столбцов, вы можете вместо этого предоставить экземпляр GridCells.Fixed
, содержащий количество требуемых столбцов.
Если ваш дизайн требует, чтобы только определенные элементы имели нестандартные размеры, вы можете использовать поддержку сетки для предоставления пользовательских диапазонов столбцов для элементов. Укажите диапазон столбцов с помощью параметра span
item
LazyGridScope DSL
и методов items
. maxLineSpan
, одно из значений области span, особенно полезно при использовании адаптивного размера, поскольку количество столбцов не фиксировано. В этом примере показано, как предоставить полный диапазон строк:
LazyVerticalGrid( columns = GridCells.Adaptive(minSize = 30.dp) ) { item(span = { // LazyGridItemSpanScope: // maxLineSpan GridItemSpan(maxLineSpan) }) { CategoryCard("Fruits") } // ... }
Ленивая шахматная сетка
LazyVerticalStaggeredGrid
и LazyHorizontalStaggeredGrid
— это компонуемые элементы, которые позволяют создавать лениво загруженную, ступенчатую сетку элементов. Ленивая вертикальная ступенчатая сетка отображает свои элементы в вертикально прокручиваемом контейнере, который охватывает несколько столбцов и позволяет отдельным элементам иметь разную высоту. Ленивые горизонтальные сетки ведут себя одинаково на горизонтальной оси с элементами разной ширины.
Следующий фрагмент представляет собой простой пример использования LazyVerticalStaggeredGrid
с шириной 200.dp
на элемент:
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() )
Чтобы задать фиксированное количество столбцов, можно использовать StaggeredGridCells.Fixed(columns)
вместо StaggeredGridCells.Adaptive
. Это делит доступную ширину на количество столбцов (или строк для горизонтальной сетки) и заставляет каждый элемент занимать эту ширину (или высоту для горизонтальной сетки):
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() )
Заполнение контента
Иногда вам понадобится добавить отступы по краям контента. Ленивые компоненты позволяют передавать некоторые PaddingValues
параметру contentPadding
для поддержки этого:
LazyColumn( contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp), ) { // ... }
В этом примере мы добавляем 16.dp
отступа к горизонтальным краям (слева и справа), а затем 8.dp
к верхней и нижней части содержимого.
Обратите внимание, что этот отступ применяется к содержимому , а не к самому LazyColumn
. В примере выше первый элемент добавит отступ 8.dp
к своей верхней части, последний элемент добавит отступ 8.dp
к своей нижней части, и все элементы будут иметь отступ 16.dp
слева и справа.
В качестве другого примера, вы можете передать PaddingValues
Scaffold
в contentPadding
LazyColumn
. Смотрите руководство edge-to-edge .
Интервал между контентом
Чтобы добавить интервал между элементами, можно использовать Arrangement.spacedBy()
. В примере ниже добавляется 4.dp
интервала между каждым элементом:
LazyColumn( verticalArrangement = Arrangement.spacedBy(4.dp), ) { // ... }
Аналогично для LazyRow
:
LazyRow( horizontalArrangement = Arrangement.spacedBy(4.dp), ) { // ... }
Однако сетки допускают как вертикальное, так и горизонтальное расположение:
LazyVerticalGrid( columns = GridCells.Fixed(2), verticalArrangement = Arrangement.spacedBy(16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp) ) { items(photos) { item -> PhotoItem(item) } }
Ключи предметов
По умолчанию состояние каждого элемента привязано к положению элемента в списке или сетке. Однако это может вызвать проблемы, если набор данных изменится, поскольку элементы, которые меняют положение, фактически теряют любое запомненное состояние. Если представить себе сценарий LazyRow
внутри LazyColumn
, если строка меняет положение элемента, пользователь затем потеряет свою позицию прокрутки внутри строки.
Чтобы бороться с этим, вы можете предоставить стабильный и уникальный ключ для каждого элемента, предоставив блок для параметра key
. Предоставление стабильного ключа позволяет обеспечить единообразие состояния элемента при изменении набора данных:
LazyColumn { items( items = messages, key = { message -> // Return a stable + unique key for the item message.id } ) { message -> MessageRow(message) } }
Предоставляя ключи, вы помогаете Compose правильно обрабатывать переупорядочения. Например, если ваш элемент содержит запомненное состояние, установка ключей позволит Compose перемещать это состояние вместе с элементом при изменении его положения.
LazyColumn { items(books, key = { it.id }) { val rememberedValue = remember { Random.nextInt() } } }
Однако есть одно ограничение на то, какие типы можно использовать в качестве ключей элементов. Тип ключа должен поддерживаться Bundle
, механизмом Android для сохранения состояний при повторном создании Activity. Bundle
поддерживает такие типы, как примитивы, перечисления или Parcelables.
LazyColumn { items(books, key = { // primitives, enums, Parcelable, etc. }) { // ... } }
Ключ должен поддерживаться Bundle
, чтобы rememberSaveable
внутри элемента composable можно было восстановить при повторном создании Activity или даже при прокрутке от этого элемента и обратно.
LazyColumn { items(books, key = { it.id }) { val rememberedValue = rememberSaveable { Random.nextInt() } } }
Анимация предметов
Если вы использовали виджет RecyclerView, вы знаете, что он автоматически анимирует изменения элементов . Ленивые макеты предоставляют ту же функциональность для переупорядочивания элементов. API прост — вам просто нужно установить модификатор animateItem
для содержимого элемента:
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()) { // ... } } }
Вы даже можете предоставить индивидуальные спецификации анимации, если вам необходимо:
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) ) ) { // ... } } }
Обязательно предоставьте ключи для своих элементов, чтобы можно было найти новое положение перемещенного элемента.
Пример: анимация элементов в ленивых списках
С помощью Compose вы можете анимировать изменения элементов в ленивых списках. При совместном использовании следующие фрагменты реализуют анимацию при добавлении, удалении и изменении порядка элементов ленивых списков.
В этом фрагменте отображается список строк с анимированными переходами при добавлении, удалении или переупорядочивании элементов:
@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), ) } } }
Ключевые моменты кодекса
-
ListAnimatedItems
отображает список строк вLazyColumn
с анимированными переходами при изменении элементов. - Функция
items
назначает уникальный ключ каждому элементу в списке. Compose использует ключи для отслеживания элементов и определения изменений в их позициях. -
ListItem
определяет макет каждого элемента списка. Он принимает параметрheadlineContent
, который определяет основное содержимое элемента. - Модификатор
animateItem
применяет анимацию по умолчанию к добавлению, удалению и перемещению предметов.
В следующем фрагменте представлен экран, включающий элементы управления для добавления и удаления элементов, а также сортировки предопределенного списка:
@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) } } }
Ключевые моменты кодекса
-
ListAnimatedItemsExample
представляет собой экран, включающий элементы управления для добавления, удаления и сортировки элементов.-
onAddItem
иonRemoveItem
— это лямбда-выражения, которые передаются вAddRemoveButtons
для добавления и удаления элементов из списка. -
resetOrder
,onSortAlphabetically
иonSortByLength
— это лямбда-выражения, которые передаются вOrderButtons
для изменения порядка элементов в списке.
-
-
AddRemoveButtons
отображает кнопки «Добавить» и «Удалить». Включает/отключает кнопки и обрабатывает нажатия кнопок. -
OrderButtons
отображает кнопки для переупорядочивания списка. Он получает лямбда-функции для сброса порядка и сортировки списка по длине или по алфавиту. -
ListAnimatedItems
вызывает составной объектListAnimatedItems
, передавая списокdata
для отображения анимированного списка строк.data
определяются в другом месте.
Этот фрагмент создает пользовательский интерфейс с кнопками «Добавить элемент» и «Удалить элемент» :
@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") } } }
Ключевые моменты кодекса
-
AddRemoveButtons
отображает ряд кнопок для выполнения операций добавления и удаления в списке. - Параметры
canAddItem
иcanRemoveItem
управляют включенным состоянием кнопок. ЕслиcanAddItem
илиcanRemoveItem
имеют значение false, то соответствующая кнопка отключена. - Параметры
onAddItem
иonRemoveItem
— это лямбда-выражения, которые выполняются, когда пользователь нажимает соответствующую кнопку.
Наконец, этот фрагмент отображает три кнопки для сортировки списка ( Сброс, По алфавиту и Длина ):
@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) } } } } }
Ключевые моменты кодекса
-
OrderButtons
отображаетSingleChoiceSegmentedButtonRow
, чтобы позволить пользователям выбрать метод сортировки в списке или сбросить порядок списка. КомпонентSegmentedButton
позволяет выбрать один вариант из списка вариантов. -
resetOrder
,orderAlphabetically
иorderByLength
— это лямбда-функции, которые выполняются при нажатии соответствующей кнопки. - Переменная состояния
selectedIndex
отслеживает выбранный параметр.
Результат
В этом видео показан результат предыдущих фрагментов при переупорядочивании элементов:
Закрепленные заголовки (экспериментальные)
Шаблон «липкого заголовка» полезен при отображении списков сгруппированных данных. Ниже вы можете увидеть пример «списка контактов», сгруппированного по инициалам каждого контакта:
Чтобы создать прикрепленный заголовок с помощью LazyColumn
, можно использовать экспериментальную функцию stickyHeader()
, указав содержимое заголовка:
@OptIn(ExperimentalFoundationApi::class) @Composable fun ListWithHeader(items: List<Item>) { LazyColumn { stickyHeader { Header() } items(items) { item -> ItemRow(item) } } }
Чтобы создать список с несколькими заголовками, как в примере «список контактов» выше, можно сделать следующее:
// 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) } } } }
Реагирование на положение прокрутки
Многим приложениям необходимо реагировать и слушать изменения положения прокрутки и макета элемента. Компоненты Lazy поддерживают этот вариант использования, поднимая LazyListState
:
@Composable fun MessageList(messages: List<Message>) { // Remember our own LazyListState val listState = rememberLazyListState() // Provide it to LazyColumn LazyColumn(state = listState) { // ... } }
Для простых случаев использования приложениям обычно нужно знать только информацию о первом видимом элементе. Для этого LazyListState
предоставляет свойства firstVisibleItemIndex
и firstVisibleItemScrollOffset
.
Если мы используем пример отображения и скрытия кнопки в зависимости от того, прокрутил ли пользователь дальше первого элемента:
@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() } } }
Чтение состояния непосредственно в композиции полезно, когда вам нужно обновить другие компонуемые элементы пользовательского интерфейса, но есть также сценарии, когда событие не нужно обрабатывать в той же композиции. Распространенным примером этого является отправка аналитического события после того, как пользователь прокрутил страницу до определенной точки. Чтобы эффективно справиться с этим, мы можем использовать snapshotFlow()
:
val listState = rememberLazyListState() LazyColumn(state = listState) { // ... } LaunchedEffect(listState) { snapshotFlow { listState.firstVisibleItemIndex } .map { index -> index > 0 } .distinctUntilChanged() .filter { it } .collect { MyAnalyticsService.sendScrolledPastFirstItemEvent() } }
LazyListState
также предоставляет информацию обо всех элементах, которые в данный момент отображаются, и их границах на экране через свойство layoutInfo
. Для получения дополнительной информации см. класс LazyListLayoutInfo
.
Управление положением прокрутки
Помимо реагирования на позицию прокрутки, приложениям также полезно иметь возможность управлять позицией прокрутки. LazyListState
поддерживает это через функцию scrollToItem()
, которая «немедленно» фиксирует позицию прокрутки, и animateScrollToItem()
, которая прокручивает с помощью анимации (также известной как плавная прокрутка):
@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) } } ) }
Большие наборы данных (пейджинг)
Библиотека Paging позволяет приложениям поддерживать большие списки элементов, загружая и отображая небольшие фрагменты списка по мере необходимости. Paging 3.0 и более поздние версии обеспечивают поддержку Compose через библиотеку androidx.paging:paging-compose
.
Чтобы отобразить список постраничного контента, мы можем использовать функцию расширения collectAsLazyPagingItems()
, а затем передать возвращенные LazyPagingItems
в items()
в нашем LazyColumn
. Подобно поддержке постраничного просмотра в представлениях, вы можете отображать заполнители во время загрузки данных, проверяя, является ли item
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() } } } }
Советы по использованию ленивых макетов
Есть несколько советов, которые вы можете принять во внимание, чтобы гарантировать, что ваши ленивые макеты будут работать так, как задумано.
Избегайте использования элементов размером 0 пикселей.
Это может произойти в сценариях, где, например, вы ожидаете асинхронного извлечения некоторых данных, таких как изображения, для заполнения элементов вашего списка на более позднем этапе. Это приведет к тому, что макет Lazy будет составлять все свои элементы в первом измерении, так как их высота составляет 0 пикселей, и он может вместить их все в область просмотра. После загрузки элементов и увеличения их высоты макеты Lazy затем отбросят все другие элементы, которые были ненужно составлены в первый раз, так как они фактически не могут вписаться в область просмотра. Чтобы избежать этого, вам следует задать размер по умолчанию для ваших элементов, чтобы макет Lazy мог выполнить правильный расчет того, сколько элементов фактически может вписаться в область просмотра:
@Composable fun Item(imageUrl: String) { AsyncImage( model = rememberAsyncImagePainter(model = imageUrl), modifier = Modifier.size(30.dp), contentDescription = null // ... ) }
Когда вы знаете приблизительный размер ваших элементов после асинхронной загрузки данных, хорошей практикой будет обеспечение того, чтобы размер ваших элементов оставался одинаковым до и после загрузки, например, путем добавления некоторых заполнителей. Это поможет сохранить правильное положение прокрутки.
Избегайте вложения компонентов, прокручиваемых в одном направлении.
Это применимо только к случаям вложения прокручиваемых дочерних элементов без предопределенного размера внутрь другого прокручиваемого в том же направлении родителя. Например, попытка вложить дочерний LazyColumn
без фиксированной высоты внутрь вертикально прокручиваемого родителя Column
:
// throws IllegalStateException Column( modifier = Modifier.verticalScroll(state) ) { LazyColumn { // ... } }
Вместо этого, тот же результат может быть достигнут путем обертывания всех ваших компонуемых элементов в один родительский LazyColumn
и использования его DSL для передачи различных типов контента. Это позволяет выдавать отдельные элементы, а также несколько элементов списка, все в одном месте:
LazyColumn { item { Header() } items(data) { item -> PhotoItem(item) } item { Footer() } }
Имейте в виду, что допускаются случаи, когда вы вкладываете макеты с разными направлениями, например, прокручиваемую родительскую Row
и дочерний LazyColumn
:
Row( modifier = Modifier.horizontalScroll(scrollState) ) { LazyColumn { // ... } }
А также случаи, когда вы по-прежнему используете те же макеты направления, но также устанавливаете фиксированный размер для вложенных дочерних элементов:
Column( modifier = Modifier.verticalScroll(scrollState) ) { LazyColumn( modifier = Modifier.height(200.dp) ) { // ... } }
Остерегайтесь помещать несколько элементов в один элемент
В этом примере вторая лямбда-функция item выдает 2 элемента в одном блоке:
LazyVerticalGrid( columns = GridCells.Adaptive(100.dp) ) { item { Item(0) } item { Item(1) Item(2) } item { Item(3) } // ... }
Ленивые макеты справятся с этим, как и ожидалось, — они будут выкладывать элементы один за другим, как будто это разные предметы. Однако есть пара проблем с этим.
Когда несколько элементов выдаются как часть одного элемента, они обрабатываются как одна сущность, что означает, что они больше не могут быть составлены по отдельности. Если один элемент становится видимым на экране, то все элементы, соответствующие элементу, должны быть составлены и измерены. Это может повредить производительности, если используется чрезмерно. В крайнем случае помещения всех элементов в один элемент, это полностью сводит на нет цель использования Lazy layouts. Помимо потенциальных проблем с производительностью, размещение большего количества элементов в одном элементе также будет мешать scrollToItem()
и animateScrollToItem()
.
Однако существуют допустимые варианты использования для размещения нескольких элементов в одном элементе, например, разделители внутри списка. Вы не хотите, чтобы разделители изменяли индексы прокрутки, поскольку они не должны считаться независимыми элементами. Кроме того, производительность не пострадает, поскольку разделители малы. Разделитель, скорее всего, должен быть видимым, когда виден предшествующий ему элемент, поэтому он может быть частью предыдущего элемента:
LazyVerticalGrid( columns = GridCells.Adaptive(100.dp) ) { item { Item(0) } item { Item(1) Divider() } item { Item(2) } // ... }
Рассмотрите возможность использования индивидуальных договоренностей
Обычно ленивые списки имеют много элементов, и они занимают больше, чем размер прокручиваемого контейнера. Однако, когда ваш список заполнен небольшим количеством элементов, ваш дизайн может иметь более конкретные требования к тому, как они должны быть расположены в области просмотра.
Чтобы добиться этого, вы можете использовать пользовательское вертикальное Arrangement
и передать его LazyColumn
. В следующем примере объект TopWithFooter
должен реализовать только метод arrange
. Во-первых, он будет располагать элементы один за другим. Во-вторых, если общая используемая высота меньше высоты области просмотра, он расположит нижний колонтитул внизу:
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() } } }
Рассмотрите возможность добавления contentType
Начиная с Compose 1.2, чтобы максимизировать производительность вашего Lazy-макета, рассмотрите возможность добавления contentType
в ваши списки или сетки. Это позволяет вам указать тип контента для каждого элемента макета в случаях, когда вы составляете список или сетку, состоящую из нескольких различных типов элементов:
LazyColumn { items(elements, contentType = { it.type }) { // ... } }
Когда вы предоставляете contentType
, Compose может повторно использовать композиции только между элементами одного типа. Поскольку повторное использование более эффективно при компоновке элементов схожей структуры, предоставление типов контента гарантирует, что Compose не будет пытаться компоновать элемент типа A поверх совершенно другого элемента типа B. Это помогает максимизировать преимущества повторного использования композиции и производительность вашего Lazy-макета.
Измерение производительности
Надежно измерить производительность Lazy-макета можно только при запуске в режиме релиза и с включенной оптимизацией R8. В отладочных сборках прокрутка Lazy-макета может выглядеть медленнее. Для получения дополнительной информации об этом прочитайте статью Производительность Compose .
Дополнительные ресурсы
- Создать конечный прокручиваемый список
- Создать прокручиваемую сетку
- Отображение вложенных прокручиваемых элементов в списке
- Фильтровать список во время ввода
- Ленивая загрузка данных с помощью списков и разбиения на страницы
- Составьте список, используя несколько типов элементов
- Видео: Списки в Compose
Рекомендовано для вас
- Примечание: текст ссылки отображается, когда JavaScript отключен.
- Перенести
RecyclerView
в список Lazy - Сохранение состояния пользовательского интерфейса в Compose
- Kotlin для Jetpack Compose