بسیاری از برنامهها باید مجموعههایی از موارد را نمایش دهند. این سند توضیح میدهد که چگونه میتوانید این کار را بهطور کارآمد در Jetpack Compose انجام دهید.
اگر میدانید مورد استفاده شما به پیمایش نیاز ندارد، میتوانید از
Column یا Row ساده (بسته به جهت) استفاده کنید و محتوای هر مورد را با
تکرار کردن فهرست به روش زیر منتشر کنید:
@Composable fun MessageList(messages: List<Message>) { Column { messages.forEach { message -> MessageRow(message) } } }
بااستفاده از اصلاحگر verticalScroll() میتوانیم Column را پیمایشپذیر کنیم.
فهرستهای تنبل
اگر نیاز دارید تعداد زیادی مورد (یا فهرستی با طول نامشخص) را نمایش دهید،
استفاده از چیدمانی مثل Column میتواند باعث بروز مشکلات عملکرد شود، زیرا همه موارد صرفنظر از اینکه قابلمشاهده باشند یا نباشند، ترکیب و چیده میشوند.
Compose مجموعهای از عناصر را ارائه میدهد که فقط عناصر قابلمشاهده در ناحیه نمایش عنصر را میسازند و چیدمان میکنند. این عناصر شامل
LazyColumn
و
LazyRow میشود.
همانطور که از نام آنها پیداست، تفاوت بین
LazyColumn
و
LazyRow
در جهت چیدمان و پیمایش آنها است. LazyColumn
فهرستی با پیمایش عمودی تولید میکند و LazyRow فهرستی با پیمایش افقی تولید میکند.
اجزای Lazy با اکثر چیدمانها در Compose متفاوت هستند. بهجای
پذیرفتن پارامتر بلوک محتوای @Composable، که به برنامهها اجازه میدهد مستقیماً
عناصر ترکیبی را منتشر کنند، عناصر «تنبل» بلوک LazyListScope.() را ارائه میدهند. این
LazyListScope
بلوک «زبان توصیفی داده» (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
از نمایش موارد در جدول پشتیبانی میکنند. «جدول عمودی تنبل»
مواردش را در یک محتوی پیمایشپذیر عمودی نمایش میدهد که
در چند ستون گسترده شده است، درحالیکه جدولهای افقی تنبل
همین رفتار را در محور افقی دارند.
جدولها قابلیتهای قدرتمند 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 از روشهای
LazyGridScope DSL
item و items مشخص کنید.
maxLineSpan،
یکی از مقادیر حوزه گسترده است، بهویژه زمانی که از
اندازهگیری تطبیقی استفاده میکنید مفید است، زیرا تعداد ستونها ثابت نیست.
این مثال نشان میدهد که چگونه میتوان یک گستره ردیف کامل ارائه کرد:
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.Adaptive از
StaggeredGridCells.Fixed(columns) استفاده کنید.
این کار باعث میشود پهنای دردسترس بر تعداد ستونها (یا ردیفها برای
جدول افقی) تقسیم شود و هر عنصر آن پهنا (یا ارتفاع برای
جدول افقی) را اشغال کند:
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 منتقل کنید. راهنمای
لبهبهلبه را ببینید.
فاصله محتوا
برای افزودن فاصله بین موارد، میتوانید از
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 اجازه میدهد وقتی موقعیت مورد تغییر میکند، این وضعیت را همراه با مورد منتقل کند.
LazyColumn { items(books, key = { it.id }) { val rememberedValue = remember { Random.nextInt() } } }
بااینحال، یک محدودیت در مورد انواع کلیدهای موردی که میتوانید استفاده کنید وجود دارد.
نوع کلید باید توسط
Bundle، سازوکار Android برای حفظ
وضعیتها هنگام بازآفرینی «فعالیت» پشتیبانی شود. Bundle از انواع مختلفی مثل ابتداییها،
شمارشها، یا Parcelable پشتیبانی میکند.
LazyColumn { items(books, key = { // primitives, enums, Parcelable, etc. }) { // ... } }
کلید باید توسط Bundle پشتیبانی شود تا وقتی «فعالیت» بازآفرینی میشود یا حتی وقتی از این مورد پیمایش میکنید و دوباره به آن برمیگردید، rememberSaveable داخل عنصر ترکیبی مورد بازیابی شود.
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) ) ) { // ... } } }
مطمئن شوید که کلیدهایی برای عناصرتان ارائه میکنید تا بتوان موقعیت جدید عنصر منتقلشده را پیدا کرد.
مثال: پویانمایی موارد در فهرستهای تنبل
با «ترکیب»، میتوانید تغییرات عناصر در فهرستهای تنبل را پویانمایی کنید. وقتی با هم استفاده شوند، گلچینهای زیر هنگام افزودن، برداشتن، و تغییر ترتیب عناصر فهرست تنبل، پویانماییها را پیادهسازی میکنند.
این گلچین فهرستی از رشتهها را با گذارهای پویانماییشده هنگام افزوده شدن، برداشته شدن، یا تغییر ترتیب موارد نمایش میدهد:
@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به هر مورد در فهرست یک کلید یکتا اختصاص میدهد. «ترکیب» از کلیدها برای پیگیری موارد و شناسایی تغییرات در موقعیت آنها استفاده میکند. 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نادرست باشد، دکمه مربوطه غیرفعال میشود. - پارامترهای
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) } } } } }
نکات کلیدی درباره کد
OrderButtonsSingleChoiceSegmentedButtonRowرا نمایش میدهد تا به کاربران امکان دهد روش مرتبسازی را در فهرست انتخاب کنند یا ترتیب فهرست را بازنشانی کنند. عنصر ASegmentedButtonبه شما امکان میدهد یک گزینه را از فهرست گزینهها انتخاب کنید.-
resetOrder،orderAlphabetically، وorderByLengthتوابع لامبدا هستند که وقتی دکمه مربوطه انتخاب میشود اجرا میشوند. - متغیر وضعیت
selectedIndexگزینه انتخابشده را پیگیری میکند.
نتیجه
این ویدیو نتیجه تکهکدهای قبلی را وقتی که عناصر مجدداً مرتب میشوند نشان میدهد:
سرایندهای چسبان
الگوی «سرصفحه چسبان» هنگام نمایش فهرستهای دادههای گروهبندیشده مفید است. در زیر میتوانید نمونهای از «فهرست مخاطبین» را ببینید که براساس حرف اول هر مخاطب گروهبندی شده است:

برای دستیابی به سرایند چسبنده با LazyColumn، میتوانید از
stickyHeader() استفاده کنید
عملکرد، ارائه محتوای سرایند:
@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] } @Composable fun ContactsList(grouped: Map<Char, List<Contact>>) { LazyColumn { grouped.forEach { (initial, contactsForInitial) -> stickyHeader { CharacterHeader(initial) } items(contactsForInitial) { contact -> ContactListItem(contact) } } } }
واکنش به موقعیت پیمایش
بسیاری از برنامهها باید به تغییرات موقعیت پیمایش و چیدمان مورد واکنش نشان دهند و به آنها گوش دهند.
عناصر «تنبل» با بالا بردن
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() } } }
خواندن مستقیم وضعیت در ترکیب زمانی مفید است که نیاز دارید
ترکیبهای دیگر واسط کاربر را بهروز کنید، اما سناریوهایی هم وجود دارد که رویداد نیازی به
مدیریت شدن در همان ترکیب ندارد. نمونه رایج این مورد ارسال رویداد
analytics پساز اینکه کاربر از نقطه خاصی رد شد است. برای مدیریت کارآمد این
موضوع، میتوانیم از
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) } } ) }
مجموعههای داده بزرگ (صفحهبندی)
کتابخانه صفحهبندی به برنامهها امکان میدهد
از فهرستهای بزرگ موارد پشتیبانی کنند و بخشهای کوچک فهرست را درصورت نیاز بار و نمایش دهند. «صفحهبندی ۳.۰» و نسخههای جدیدتر ازطریق کتابخانه
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() } } } }
نکتههایی درباره استفاده از چیدمانهای تنبل
چند نکته وجود دارد که میتوانید درنظر بگیرید تا مطمئن شوید چیدمانهای «تنبل» شما همانطور که درنظر گرفته شده است کار میکنند.
از استفاده از عناصر با اندازه ۰ پیکسل خودداری کنید
این میتواند در سناریوهایی اتفاق بیفتد که، برای مثال، انتظار دارید بهصورت ناهمزمان برخیاز دادهها مثل تصاویر را بازیابی کنید تا موارد فهرستتان را در مرحله بعدی تکمیل کنید. این کار باعث میشود چیدمان «تنبل» همه عناصرش را در اولین اندازهگیری بسازد، زیرا ارتفاع آنها ۰ پیکسل است و میتواند همه آنها را در درگاه دید جا دهد. وقتی عناصر بارگذاری شدند و ارتفاع آنها افزایش یافت، چیدمانهای «تنبل» همه عناصر دیگری را که در اولین بار بهطور غیرضروری ترکیب شدهاند دور میاندازند زیرا درواقع نمیتوانند در درگاه دید جا شوند. برای جلوگیری از این اتفاق، باید اندازه پیشفرض را برای عناصرتان تنظیم کنید تا چیدمان «تنبل» بتواند محاسبه صحیح تعداد عناصری را که درواقع میتوانند در درگاه دید جا شوند انجام دهد:
@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) ) { // ... } }
مراقب باشید که چند عنصر را در یک مورد قرار ندهید
در این مثال، لامبدای مورد دوم ۲ مورد را در یک بلوک منتشر میکند:
LazyVerticalGrid( columns = GridCells.Adaptive(100.dp) ) { item { Item(0) } item { Item(1) Item(2) } item { Item(3) } // ... }
چیدمانهای تنبل این کار را همانطور که انتظار میرود انجام میدهند - عناصر را یکی پس از دیگری میچینند، انگار که موارد مختلفی هستند. بااینحال، انجام این کار چند مشکل دارد.
وقتی چندین عنصر بهعنوان بخشی از یک مورد منتشر میشوند، بهعنوان یک نهاد مدیریت میشوند، یعنی دیگر نمیتوانند بهصورت جداگانه ترکیب شوند. اگر یک عنصر در صفحهنمایش نمایان شود، همه عناصر مربوط به آن مورد باید ترکیب و اندازهگیری شوند. اگر بیشازحد استفاده شود، میتواند به عملکرد آسیب برساند. در حالت افراطی قرار دادن همه عناصر در یک مورد، هدف استفاده از چیدمانهای «تنبل» را کاملاً ازبین میبرد. علاوهبر مشکلات احتمالی عملکرد، قرار دادن عناصر بیشتر در یک مورد با 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 اضافه کنید
از «نوشتن» نسخه ۱.۲، برای بیشینه کردن عملکرد چیدمان Lazy،
contentType
را به فهرستها یا شبکههایتان اضافه کنید. این امکان را به شما میدهد تا نوع محتوا را برای هر
مورد از چیدمان مشخص کنید، در مواردی که فهرستی یا شبکهای متشکل از
چندین نوع مختلف از موارد را میسازید:
LazyColumn { items(elements, contentType = { it.type }) { // ... } }
وقتی
contentType را ارائه میدهید،
«نگارش» فقط میتواند از قطعههای موسیقی
بین موارد از یک نوع استفاده مجدد کند. ازآنجاییکه وقتی عناصر با ساختار مشابه را
مینویسید، استفاده مجدد کارآمدتر است، ارائه انواع محتوا تضمین میکند
«نوشتن» سعی نمیکند عنصر نوع A را روی عنصر کاملاً
متفاوتی از نوع B بنویسد. این کار به حداکثر رساندن مزایای ترکیب
استفاده مجدد و عملکرد چیدمان تنبل شما کمک میکند.
درحال اندازهگیری عملکرد
فقط زمانی میتوانید عملکرد چیدمان «تنبل» را بهطور قابلاعتمادی اندازهگیری کنید که در حالت انتشار اجرا شود و بهینهسازی R8 فعال باشد. در ساختهای اشکالزدایی، پیمایش چیدمان «تنبل» ممکن است کندتر بهنظر برسد. برای اطلاعات بیشتر درباره این موضوع، عملکرد نوشتن را بخوانید.
منابع بیشتر
- ایجاد فهرست پیمایششدنی محدود
- ایجاد شبکه پیمایشپذیر
- نمایش موارد پیمایش تودرتو در فهرست
- فیلتر کردن فهرست درحین تایپ کردن
- بار کردن تنبلوار دادهها با فهرستها و صفحهبندی
- ساختن فهرست بااستفاده از چند نوع مورد
- ویدیو: فهرستها در «نوشتن»
توصیهشده برای شما
- توجه: نوشتار پیوند وقتی جاوا اسکریپت خاموش است نمایش داده میشود
- انتقال
RecyclerViewبه فهرست تنبل - ذخیره وضعیت میانای کاربر در Compose
- Kotlin برای Jetpack Compose