Birçok uygulamanın öğe koleksiyonları göstermesi gerekir. Bu dokümanda, bunu Jetpack Compose'da nasıl verimli bir şekilde yapabileceğiniz açıklanmaktadır.
Kullanım alanınızda kaydırma gerekmediğini biliyorsanız basit bir Column
veya Row
(yöne bağlı olarak) kullanıp bir liste üzerinde aşağıdaki şekilde iterasyon yaparak her öğenin içeriğini yayınlayabilirsiniz:
@Composable fun MessageList(messages: List<Message>) { Column { messages.forEach { message -> MessageRow(message) } } }
verticalScroll()
değiştiricisini kullanarak Column
'u kaydırılabilir hale getirebiliriz.
Tembel listeler
Çok sayıda öğe (veya bilinmeyen uzunluktaki bir liste) göstermeniz gerekiyorsa Column
gibi bir düzen kullanmak performans sorunlarına neden olabilir. Bunun nedeni, tüm öğelerin görünür olup olmadığına bakılmaksızın derlenmesi ve yerleştirilmesidir.
Oluşturma, yalnızca bileşenin görüntü alanında görünen öğeleri oluşturan ve düzenleyen bir bileşen grubu sağlar. Bu bileşenler arasında LazyColumn
ve LazyRow
yer alır.
Adından da anlaşılacağı gibi, LazyColumn
ile LazyRow
arasındaki fark, öğelerin düzenlenme ve kaydırma yönüdür. LazyColumn
dikey kaydırmalı bir liste, LazyRow
ise yatay kaydırmalı bir liste oluşturur.
Yavaş bileşenler, Oluştur'daki çoğu düzenden farklıdır. Uygulamaların doğrudan bileşen yayınlamasına olanak tanıyan bir @Composable
içerik bloğu parametresi kabul etmek yerine, yavaş bileşenler bir LazyListScope.()
bloğu sağlar. Bu LazyListScope
bloku, uygulamaların öğe içeriklerini açıklamasına olanak tanıyan bir DSL sunar. Ardından, tembel bileşen her öğenin içeriğini düzen ve kaydırma konumu gerektirdiği şekilde eklemekten sorumludur.
LazyListScope
DSL
LazyListScope
DSL'si, düzendeki öğeleri tanımlamak için çeşitli işlevler sağlar. En basit haliyle, item()
tek bir öğe, items(Int)
ise birden fazla öğe ekler:
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
gibi öğe koleksiyonları eklemenize olanak tanıyan çeşitli uzantı işlevleri de vardır. Bu uzantılar, yukarıdaki Column
örneğimizi kolayca taşımamıza olanak tanır:
/** * import androidx.compose.foundation.lazy.items */ LazyColumn { items(messages) { message -> MessageRow(message) } }
items()
uzantı işlevinin itemsIndexed()
adlı bir varyantı da vardır. Bu varyant, dizini sağlar. Daha fazla bilgi için lütfen LazyListScope
referansına bakın.
Yavaş ızgaralar
LazyVerticalGrid
ve LazyHorizontalGrid
bileşenleri, öğeleri ızgarada görüntüleme desteği sağlar. Yavaş dikey ızgaralar öğelerini dikey olarak kaydırılabilir bir kapsayıcıda, birden fazla sütuna yayılmış şekilde gösterir. Yavaş yatay ızgaralar ise yatay eksende aynı davranışı gösterir.
Tablolar, listelerle aynı güçlü API özelliklerine sahiptir ve içeriği açıklamak için çok benzer bir DSL'yi (LazyGridScope.()
) kullanır.
LazyVerticalGrid
içindeki columns
parametresi ve LazyHorizontalGrid
içindeki rows
parametresi, hücrelerin sütun veya satır halinde nasıl oluşturulacağını kontrol eder. Aşağıdaki örnekte, her sütunu en az 128.dp
genişliğinde ayarlamak için GridCells.Adaptive
kullanılarak öğeler ızgara şeklinde gösterilmektedir:
LazyVerticalGrid( columns = GridCells.Adaptive(minSize = 128.dp) ) { items(photos) { photo -> PhotoItem(photo) } }
LazyVerticalGrid
, öğeler için bir genişlik belirtmenize olanak tanır. Ardından ızgara, mümkün olduğunca çok sütuna sığdırılır. Kalan genişlik, sütun sayısı hesaplandıktan sonra sütunlar arasında eşit olarak dağıtılır.
Bu uyarlanabilir boyutlandırma yöntemi, özellikle farklı ekran boyutlarında öğe grupları görüntülemek için yararlıdır.
Kullanılacak sütun sayısını tam olarak biliyorsanız bunun yerine gerekli sütun sayısını içeren bir GridCells.Fixed
örneği sağlayabilirsiniz.
Tasarımınızda yalnızca belirli öğelerin standart dışı boyutlara sahip olması gerekiyorsa öğeler için özel sütun aralıkları sağlamak üzere ızgara desteğini kullanabilirsiniz.
Sütun kapsamını, LazyGridScope DSL
item
ve items
yöntemlerinin span
parametresiyle belirtin.
Aralık kapsamının değerlerinden biri olan maxLineSpan
, sütun sayısı sabit olmadığından özellikle uyarlanabilir boyutlandırmayı kullanırken kullanışlıdır.
Bu örnekte, tam satır kapsamının nasıl sağlanacağı gösterilmektedir:
LazyVerticalGrid( columns = GridCells.Adaptive(minSize = 30.dp) ) { item(span = { // LazyGridItemSpanScope: // maxLineSpan GridItemSpan(maxLineSpan) }) { CategoryCard("Fruits") } // ... }
Eğik ızgara
LazyVerticalStaggeredGrid
ve
LazyHorizontalStaggeredGrid
, öğelerden oluşan, gecikmeli yüklenmiş, kademeli bir ızgara oluşturmanıza olanak tanıyan birleştirilebilir öğelerdir.
Yavaş dikey kademeli ızgara, öğelerini birden fazla sütuna yayılan ve öğelerin farklı yüksekliklere sahip olmasına olanak tanıyan dikey olarak kaydırılabilir bir kapsayıcıda gösterir. Yavaş yatay ızgaralar, farklı genişlikteki öğelerin bulunduğu yatay eksende aynı davranışı gösterir.
Aşağıdaki snippet, öğe başına 200.dp
genişliğinde LazyVerticalStaggeredGrid
kullanmanın temel bir örneğidir:
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() )
Sabit bir sütun sayısı ayarlamak için StaggeredGridCells.Adaptive
yerine StaggeredGridCells.Fixed(columns)
kullanabilirsiniz.
Bu işlem, mevcut genişliği sütun sayısına (veya yatay ızgara için satır sayısına) böler ve her bir öğenin bu genişliği (veya yatay ızgara için yüksekliği) kaplamasını sağlar:
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() )
![Oluştur'daki resimlerin ızgara şeklinde yan yana sarmalanması](https://developer.android.google.cn/static/develop/ui/compose/images/lists/staggered_grid_fixed.png?authuser=3&hl=tr)
İçerik doldurma
Bazen içeriğin kenarlarına dolgu eklemeniz gerekir. Yavaş bileşenler, bunu desteklemek için contentPadding
parametresine bazı PaddingValues
iletmenize olanak tanır:
LazyColumn( contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp), ) { // ... }
Bu örnekte, yatay kenarlara (sol ve sağ) 16.dp
, ardından içeriğin üst ve alt tarafına 8.dp
dolgu ekliyoruz.
Bu dolgunun LazyColumn
'e değil, içeriğe uygulandığını lütfen unutmayın. Yukarıdaki örnekte, ilk öğe üst kısmına 8.dp
dolgu, son öğe alt kısmına 8.dp
dolgu, tüm öğeler ise sol ve sağ tarafına 16.dp
dolgu ekler.
Başka bir örnek olarak, Scaffold
'nin PaddingValues
öğesini LazyColumn
'nin contentPadding
öğesine iletebilirsiniz. Kenardan kenara kılavuzunu inceleyin.
İçerik aralığı
Öğeler arasına boşluk eklemek için Arrangement.spacedBy()
simgesini kullanabilirsiniz.
Aşağıdaki örnekte, her öğe arasına 4.dp
boşluk eklenmiştir:
LazyColumn( verticalArrangement = Arrangement.spacedBy(4.dp), ) { // ... }
LazyRow
için de benzer şekilde:
LazyRow( horizontalArrangement = Arrangement.spacedBy(4.dp), ) { // ... }
Ancak ızgaralar hem dikey hem de yatay düzenlemeleri kabul eder:
LazyVerticalGrid( columns = GridCells.Fixed(2), verticalArrangement = Arrangement.spacedBy(16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp) ) { items(photos) { item -> PhotoItem(item) } }
Öğe anahtarları
Varsayılan olarak her öğenin durumu, öğenin listedeki veya tablodaki konumuna göre anahtarlanır. Ancak, konumu değişen öğeler hatırlanan durumu etkili bir şekilde kaybettiği için veri kümesi değişirse bu durum sorunlara neden olabilir. Bir LazyColumn
içinde LazyRow
senaryosunun olduğunu düşünürseniz satır öğe konumunu değiştirirse kullanıcı satırdaki kaydırma konumunu kaybeder.
Bununla mücadele etmek için her öğe için key
parametresine bir blok ekleyerek sabit ve benzersiz bir anahtar sağlayabilirsiniz. Sabit bir anahtar sağlamak, öğe durumunun veri kümesi değişikliklerinde tutarlı olmasını sağlar:
LazyColumn { items( items = messages, key = { message -> // Return a stable + unique key for the item message.id } ) { message -> MessageRow(message) } }
Anahtarlar sağlayarak Compose'un yeniden sıralamayı doğru şekilde işlemesine yardımcı olursunuz. Örneğin, öğeniz hatırlanan durum içeriyorsa ayar anahtarları, konumu değiştiğinde Oluştur'un bu durumu öğeyle birlikte taşımasına olanak tanır.
LazyColumn { items(books, key = { it.id }) { val rememberedValue = remember { Random.nextInt() } } }
Ancak öğe anahtarı olarak kullanabileceğiniz türlerle ilgili bir sınırlama vardır.
Anahtarın türü, etkinlik yeniden oluşturulduğunda durumları korumak için Android'in mekanizması olan Bundle
tarafından desteklenmelidir. Bundle
, ilkel, enum veya Parcelables gibi türleri destekler.
LazyColumn { items(books, key = { // primitives, enums, Parcelable, etc. }) { // ... } }
Anahtar, Bundle
tarafından desteklenmelidir. Böylece, etkinlik yeniden oluşturulduğunda veya bu öğeden uzaklaşıp geri geldiğinizde bile, öğe bileşiğinin içindeki rememberSaveable
geri yüklenebilir.
LazyColumn { items(books, key = { it.id }) { val rememberedValue = rememberSaveable { Random.nextInt() } } }
Öğe animasyonları
RecyclerView widget'ını kullandıysanız öğe değişikliklerini otomatik olarak animasyonlu hale getirdiğini bilirsiniz.
Yavaş düzenler, öğelerin yeniden sıralanmasında aynı işlevi sağlar.
API basittir. animateItem
değiştiricisini öğe içeriğine ayarlamanız yeterlidir:
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()) { // ... } } }
Gerekirse özel animasyon spesifikasyonu da sağlayabilirsiniz:
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) ) ) { // ... } } }
Taşınan öğenin yeni konumunu bulmak için öğeleriniz için anahtar sağladığınızdan emin olun.
Örnek: Tembel listelerdeki öğelerin animasyonu
Oluştur özelliğiyle, yavaş listelerdeki öğelerde yapılan değişiklikleri animasyonlu olarak gösterebilirsiniz. Aşağıdaki snippet'ler birlikte kullanıldığında, tembel liste öğeleri eklenirken, kaldırılırken ve yeniden sıralanırken animasyonlar uygulanır.
Bu snippet, öğe eklendiğinde, kaldırıldığında veya yeniden sıralandığında animasyonlu geçişler içeren bir dize listesi gösterir:
@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), ) } } }
Kodla ilgili önemli noktalar
ListAnimatedItems
, öğeler değiştirildiğinde animasyonlu geçişlerleLazyColumn
içinde dizelerin listesini gösterir.items
işlevi, listedeki her öğeye benzersiz bir anahtar atar. Oluştur, öğeleri takip etmek ve konumlarındaki değişiklikleri belirlemek için tuşları kullanır.ListItem
, her liste öğesinin düzenini tanımlar. Öğenin ana içeriğini tanımlayan birheadlineContent
parametresi alır.animateItem
değiştiricisi, öğe ekleme, kaldırma ve taşıma işlemlerine varsayılan animasyonlar uygular.
Aşağıdaki snippet'te, öğe ekleme ve kaldırmanın yanı sıra önceden tanımlanmış bir listeyi sıralama kontrollerini içeren bir ekran gösterilmektedir:
@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) } } }
Kodla ilgili önemli noktalar
ListAnimatedItemsExample
, öğe ekleme, kaldırma ve sıralama denetimlerini içeren bir ekran gösterir.onAddItem
veonRemoveItem
, listede öğe eklemek ve kaldırmak içinAddRemoveButtons
işlevine iletilen lambda ifadeleridir.resetOrder
,onSortAlphabetically
veonSortByLength
, listedeki öğelerin sırasını değiştirmek içinOrderButtons
'e iletilen lambda ifadeleridir.
AddRemoveButtons
, "Ekle" ve "Kaldır" düğmelerini gösterir. Düğmeleri etkinleştirir/devre dışı bırakır ve düğme tıklamalarını işler.OrderButtons
, listeyi yeniden sıralama düğmelerini gösterir. Sırayı sıfırlamak ve listeyi uzunluğa veya alfabetik olarak sıralamak için lambda işlevlerini alır.ListAnimatedItems
, animasyonlu dize listesini görüntülemek içinListAnimatedItems
listesini geçirerekListAnimatedItems
composable'ını çağırır.data
data
başka bir yerde tanımlanmışsa.
Bu snippet, Öğe Ekle ve Öğe Sil düğmelerini içeren bir kullanıcı arayüzü oluşturur:
@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") } } }
Kodla ilgili önemli noktalar
AddRemoveButtons
, listede ekleme ve kaldırma işlemleri yapmak için bir düğme satırı gösterir.canAddItem
vecanRemoveItem
parametreleri, düğmelerin etkin durumunu kontrol eder.canAddItem
veyacanRemoveItem
yanlış ise ilgili düğme devre dışı bırakılır.onAddItem
veonRemoveItem
parametreleri, kullanıcı ilgili düğmeyi tıkladığında çalıştırılan lambdalardır.
Son olarak bu snippet'te, listeyi sıralamak için üç düğme (Sıfırla, Alfabetik ve Uzunluk) gösterilir:
@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) } } } } }
Kodla ilgili önemli noktalar
OrderButtons
, kullanıcıların listede bir sıralama yöntemi seçmesine veya liste sırasını sıfırlamasına olanak tanımak içinSingleChoiceSegmentedButtonRow
gösterir.SegmentedButton
bileşeni, seçenek listesinden tek bir seçenek belirlemenizi sağlar.resetOrder
,orderAlphabetically
veorderByLength
, ilgili düğme seçildiğinde çalıştırılan lambda işlevleridir.selectedIndex
durum değişkeni, seçilen seçeneği izler.
Sonuç
Bu videoda, öğeler yeniden sıralandığında önceki snippet'lerin sonucu gösterilmektedir:
Yapışkan başlıklar (deneysel)
"Yapışkan başlık" kalıbı, gruplandırılmış verilerin listelerini görüntülerken kullanışlıdır. Aşağıda, her kişinin baş harfine göre gruplandırılmış bir "kişiler listesi" örneğini görebilirsiniz:
LazyColumn
ile yapışkan bir başlık elde etmek için deneme amaçlı stickyHeader()
işlevini kullanarak başlık içeriğini sağlayabilirsiniz:
@OptIn(ExperimentalFoundationApi::class) @Composable fun ListWithHeader(items: List<Item>) { LazyColumn { stickyHeader { Header() } items(items) { item -> ItemRow(item) } } }
Yukarıdaki "kişi listesi" örneği gibi birden çok üstbilgi içeren bir liste oluşturmak için şunları yapabilirsiniz:
// 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) } } } }
Kaydırma konumuna tepki verme
Birçok uygulamanın kaydırma konumu ve öğe düzeni değişikliklerine tepki vermesi ve bu değişiklikleri dinlemesi gerekir.
Tembel bileşenler, LazyListState
öğesini kaldırarak bu kullanım alanını destekler:
@Composable fun MessageList(messages: List<Message>) { // Remember our own LazyListState val listState = rememberLazyListState() // Provide it to LazyColumn LazyColumn(state = listState) { // ... } }
Basit kullanım alanlarında, uygulamaların genellikle yalnızca ilk görünür öğeyle ilgili bilgileri bilmesi gerekir. Bunun için LazyListState
, firstVisibleItemIndex
ve firstVisibleItemScrollOffset
özelliklerini sağlar.
Kullanıcının ilk öğeyi geçip geçmediğine bağlı olarak bir düğmeyi gösterme ve gizleme örneğini kullanırsak:
@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() } } }
Durumu doğrudan bileşimde okumak, diğer kullanıcı arayüzü bileşenlerini güncellemeniz gerektiğinde kullanışlıdır ancak etkinliğin aynı bileşimde ele alınması gerekmeyen senaryolar da vardır. Bunun yaygın bir örneği, kullanıcı belirli bir noktanın ötesine geçtiğinde bir Analytics etkinliği göndermektir. Bu işlemi verimli bir şekilde gerçekleştirmek için snapshotFlow()
kullanabiliriz:
val listState = rememberLazyListState() LazyColumn(state = listState) { // ... } LaunchedEffect(listState) { snapshotFlow { listState.firstVisibleItemIndex } .map { index -> index > 0 } .distinctUntilChanged() .filter { it } .collect { MyAnalyticsService.sendScrolledPastFirstItemEvent() } }
LazyListState
, layoutInfo
mülkü aracılığıyla şu anda gösterilen tüm öğeler ve ekrandaki sınırları hakkında da bilgi sağlar. Daha fazla bilgi için LazyListLayoutInfo
sınıfına bakın.
Kaydırma konumunu kontrol etme
Uygulamaların kaydırma konumuna tepki vermesinin yanı sıra kaydırma konumunu kontrol edebilmesi de yararlıdır.
LazyListState
, kaydırma konumunu "hemen" sabitleyen ve animasyon (sürüklemeyi kolaylaştıran kaydırma olarak da bilinir) kullanarak kaydıran scrollToItem()
işlevi aracılığıyla bunu destekler: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) } } ) }
Büyük veri kümeleri (sayfalama)
Sayfalama kitaplığı, uygulamaların büyük öğe listelerini desteklemesini sağlar. Bu kitaplık, gerektiğinde listenin küçük parçalarını yükleyip gösterir. Sayfalandırma 3.0 ve sonraki sürümler, androidx.paging:paging-compose
kitaplığı aracılığıyla Oluştur desteği sağlar.
Sayfalandırılmış içeriğin listesini görüntülemek için collectAsLazyPagingItems()
uzantı işlevini kullanabilir ve döndürülen LazyPagingItems
değerini LazyColumn
öğemizdeki items()
'e iletebiliriz. Görünümlerdeki Sayfalama desteğine benzer şekilde, item
değerinin null
olup olmadığını kontrol ederek veriler yüklenirken yer tutucular gösterebilirsiniz:
@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() } } } }
Yavaş düzenleri kullanmayla ilgili ipuçları
Yavaş oluşturulan düzenlerinizin amaçlandığı gibi çalıştığından emin olmak için dikkate alabileceğiniz birkaç ipucu vardır.
0 piksel boyutunda öğeler kullanmaktan kaçının
Bu durum, örneğin listenizin öğelerini daha sonraki bir aşamada doldurmak için resimler gibi bazı verileri eşzamansız olarak almanız beklenen senaryolarda ortaya çıkabilir. Bu durumda, öğelerin yüksekliği 0 piksel olduğundan ve tüm öğeler görüntü alanına sığabileceğinden, tembel düzenin tüm öğelerini ilk ölçümde oluşturmasına neden olur. Öğeler yüklendikten ve yükseklikleri genişletildikten sonra, tembel düzenler, görüntü alanına sığmadıkları için ilk kez gereksiz yere oluşturulmuş diğer tüm öğeleri atar. Bunu önlemek için öğelerinize varsayılan boyutlandırmayı ayarlamanız gerekir. Böylece, tembel düzen, görüntü alanına kaç öğenin sığabileceğini doğru şekilde hesaplayabilir:
@Composable fun Item(imageUrl: String) { AsyncImage( model = rememberAsyncImagePainter(model = imageUrl), modifier = Modifier.size(30.dp), contentDescription = null // ... ) }
Veriler eşzamansız olarak yüklendikten sonra öğelerinizin yaklaşık boyutunu bildiğinizde, öğelerinizin boyutunun yükleme öncesi ve sonrası aynı kalmasını sağlamak için bazı yer tutucular ekleyerek iyi bir uygulama yapabilirsiniz. Bu sayede, kaydırma çubuğunun doğru konumda kalması sağlanır.
Aynı yönde kaydırılabilir bileşenleri iç içe yerleştirmekten kaçının
Bu durum yalnızca, önceden tanımlanmış bir boyut olmadan kaydırılabilir alt öğelerin aynı yönde kaydırılabilir başka bir üst öğe içine yerleştirildiği durumlarda geçerlidir. Örneğin, dikey olarak kaydırılabilir bir Column
üst öğenin içine sabit yükseklik içermeyen bir LazyColumn
alt öğe yerleştirmeye çalışmak:
// throws IllegalStateException Column( modifier = Modifier.verticalScroll(state) ) { LazyColumn { // ... } }
Bunun yerine, tüm derlenebilir öğelerinizi tek bir üst LazyColumn
içine sarmalayarak ve farklı içerik türlerini iletmek için DSL'sini kullanarak aynı sonuca ulaşabilirsiniz. Bu sayede tek öğelerin yanı sıra birden fazla liste öğesini tek bir yerden yayınlayabilirsiniz:
LazyColumn { item { Header() } items(data) { item -> PhotoItem(item) } item { Footer() } }
Farklı yön düzenlerini iç içe yerleştirdiğiniz durumlarda (ör. kaydırılabilir bir üst öğe Row
ve bir alt öğe LazyColumn
) izin verildiğini unutmayın:
Row( modifier = Modifier.horizontalScroll(scrollState) ) { LazyColumn { // ... } }
Aynı yön düzenlerini kullanmaya devam ettiğiniz ancak iç içe yerleştirilmiş alt öğelere sabit bir boyut da atadığınız durumlarda da:
Column( modifier = Modifier.verticalScroll(scrollState) ) { LazyColumn( modifier = Modifier.height(200.dp) ) { // ... } }
Bir öğeye birden fazla öğe eklemekten kaçının
Bu örnekte, ikinci öğe lambda'sı tek bir blokta 2 öğe yayınlar:
LazyVerticalGrid( columns = GridCells.Adaptive(100.dp) ) { item { Item(0) } item { Item(1) Item(2) } item { Item(3) } // ... }
Tembel düzenler bunu beklendiği gibi ele alır. Öğeleri farklı öğelermiş gibi birbiri ardına düzenler. Ancak bunu yapmanın birkaç sorunu vardır.
Birden fazla öğe tek bir öğenin parçası olarak yayınlandığında tek bir varlık olarak ele alınır. Bu nedenle, artık tek tek derlenemezler. Ekranda bir öğe görünür hale gelirse öğeye karşılık gelen tüm öğelerin oluşturulması ve ölçülmesi gerekir. Bu, aşırı kullanıldığında performansı olumsuz etkileyebilir. Tüm öğeleri tek bir öğeye yerleştirmek gibi uç bir durumda, yavaş düzenlerin kullanılmasının amacı tamamen ortadan kalkar. Olası performans sorunlarının yanı sıra, bir öğeye daha fazla öğe eklemek scrollToItem()
ve animateScrollToItem()
ile de etkileşime girer.
Bununla birlikte, bir öğeye birden fazla öğe eklemenin geçerli kullanım alanları vardır (ör. bir listede ayırıcılar kullanmak). Bağımsız öğeler olarak kabul edilmemeleri gerektiğinden, bölücülerin kaydırma dizelerini değiştirmesini istemezsiniz. Ayrıca, bölücüler küçük olduğu için performans da etkilenmez. Bölücünün, önceki öğenin parçası olabilmesi için önceki öğe görünür olduğunda görünür olması gerekir:
LazyVerticalGrid( columns = GridCells.Adaptive(100.dp) ) { item { Item(0) } item { Item(1) Divider() } item { Item(2) } // ... }
Özel düzenlemeler kullanmayı düşünün
Genellikle, yavaş listeler çok sayıda öğe içerir ve kaydırılabilir kapsayıcının boyutundan daha fazla yer kaplar. Ancak listeniz az sayıda öğeyle doldurulduğunda, bunların görüntü alanında nasıl konumlandırılacağıyla ilgili tasarımınız için daha spesifik gereksinimler olabilir.
Bunu yapmak için özel dikey Arrangement
kullanabilir ve LazyColumn
'a iletebilirsiniz. Aşağıdaki örnekte, TopWithFooter
nesnesinin yalnızca arrange
yöntemini uygulaması gerekir. Öncelikle öğeleri birbiri ardına yerleştirir. İkinci olarak, kullanılan toplam yükseklik görüntü alanı yüksekliğinden düşükse altbilgi en alta yerleştirilir:
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
ekleyebilirsiniz
Compose 1.2'den itibaren, yavaş düzeninizin performansını en üst düzeye çıkarmak için listelerinize veya ızgaralarınıza contentType
ekleyebilirsiniz. Bu sayede, birden fazla farklı öğe türünden oluşan bir liste veya ızgara oluşturduğunuzda düzenin her bir öğesinin içerik türünü belirtebilirsiniz:
LazyColumn { items(elements, contentType = { it.type }) { // ... } }
contentType
sağladığınızda, Compose yalnızca aynı türdeki öğeler arasında kompozisyonları yeniden kullanabilir. Benzer yapıya sahip öğeler oluşturduğunuzda yeniden kullanma daha verimli olduğundan, içerik türlerini belirtmek, Oluştur'un tamamen farklı bir B türü öğenin üzerine A türü bir öğe oluşturmaya çalışmamasını sağlar. Bu sayede, kompozisyonları yeniden kullanmanın ve yavaş düzen performansınızın avantajlarını en üst düzeye çıkarabilirsiniz.
Performansı ölçme
Yavaşlamalı bir düzenin performansını yalnızca yayın modunda ve R8 optimizasyonu etkinken güvenilir bir şekilde ölçebilirsiniz. Hata ayıklama derlemelerinde, gecikmeli düzen kaydırma daha yavaş görünebilir. Bu konuda daha fazla bilgi edinmek için Oluşturma performansı başlıklı makaleyi okuyun.
Sizin için önerilenler
- Not: JavaScript kapalıyken bağlantı metni gösterilir
RecyclerView
öğesini Tembel listeye taşıma- Oluştur'da kullanıcı arayüzü durumunu kaydetme
- Jetpack Compose için Kotlin