بسیاریاز برنامهها باید مجموعههایی از موارد را نمایش دهند. این سند توضیح میدهد که چگونه میتوانید این کار را بهطور مؤثر در 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، که به برنامهها اجازه میدهد مستقیماً
ترکیبپذیرها را منتشر کنند، مؤلفههای Lazy یک بلوک LazyListScope.() ارائه میدهند. این
LazyListScope
بلوک یک DSL ارائه میدهد که به برنامهها اجازه میدهد محتوای مورد را توصیف کنند. سپس مؤلفه Lazy مسئول افزودن محتوای هر مورد است، همانطور که
توسط چیدمان و موقعیت پیمایش لازم است.
LazyListScope DSL
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 vertical grid
موارد خود را در یک محفظه قابلپیمایش عمودی نمایش میدهد که در چندین ستون گسترده شده است، درحالیکه Lazy horizontal grids همان رفتار را در محور افقی خواهند داشت.
شبکهها همان قابلیتهای 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.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 منتقل کنید. راهنمای
لبهبهلبه را ببینید.
فاصلهگذاری محتوا
برای افزودن فاصله بین موارد، میتوانید از
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 برای حفظ وضعیتها هنگام بازآفرینی «فعالیت». Bundle از انواع دادههایی مثل دادههای اولیه،
enums، یا Parcelables پشتیبانی میکند.
LazyColumn { items(books, key = { // primitives, enums, Parcelable, etc. }) { // ... } }
کلید باید توسط Bundle پشتیبانی شود تا rememberSaveable داخل
مورد قابلترکیب بتواند هنگام بازآفرینی «فعالیت» یا حتی
هنگامی که از این مورد دور میشوید و دوباره به آن برمیگردید، بازیابی شود.
LazyColumn { items(books, key = { it.id }) { val rememberedValue = rememberSaveable { Random.nextInt() } } }
پویانماییهای مورد
اگر از ابزاره RecyclerView استفاده کرده باشید، میدانید که تغییرات مورد را بهطور خودکار پویانمایی میکند.
چیدمانهای Lazy همین عملکرد را برای مرتبسازی مجدد موارد ارائه میدهند.
این میانای برنامهسازی کاربردی ساده است - فقط باید اصلاحکننده
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نادرست باشد، دکمه مربوطه غیرفعال میشود. - پارامترهای
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را نمایش میدهد تا کاربران بتوانند روش مرتبسازی را در فهرست انتخاب کنند یا ترتیب فهرست را بازنشانی کنند. مؤلفهSegmentedButtonبه شما امکان میدهد یک گزینه را از فهرست گزینهها انتخاب کنید.-
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) } } } }
واکنش به موقعیت پیمایش
بسیاری از برنامهها باید به تغییرات موقعیت پیمایش و چیدمان موارد واکنش نشان دهند و آنها را گوش دهند.
مؤلفههای 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
را ببینید.
کنترل کردن موقعیت پیمایش
As well as reacting to scroll position, it’s also useful for apps to be able to
control the scroll position too.
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 3.0 و نسخههای جدیدتر ازطریق کتابخانه androidx.paging:paging-compose از 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() } } } }
نکاتی درباره استفاده از چیدمانهای Lazy
چند نکته وجود دارد که میتوانید برای اطمینان از اینکه چیدمانهای Lazy شما طبق انتظار کار میکنند، درنظر بگیرید.
از استفاده از موارد با اندازه ۰ پیکسل خودداری کنید
این اتفاق میتواند در سناریوهایی رخ دهد که، برای مثال، انتظار دارید بهصورت ناهمگام برخی دادهها مانند تصاویر را بازیابی کنید تا در مرحله بعدی موارد فهرستتان را پر کنید. این کار باعث میشود که چیدمان Lazy همه موارد خود را در اولین اندازهگیری ترکیب کند، زیرا ارتفاع آنها ۰ پیکسل است و میتواند همه آنها را در نمای دید جا دهد. وقتی موارد بارگیری شدند و ارتفاع آنها افزایش یافت، چیدمانهای 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 { // ... } }
درعوض، میتوان با قرار دادن همه composableها در یک 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) } // ... }
چیدمانهای تنبل این کار را طبق انتظار انجام میدهند - آنها عناصر را یکی پساز دیگری بهگونهای میچینند که گویی موارد متفاوتی هستند. بااینحال، چند مشکل در انجام این کار وجود دارد.
وقتی چند عنصر بهعنوان بخشی از یک مورد منتشر میشوند، بهعنوان یک موجودیت مدیریت میشوند، یعنی دیگر نمیتوان آنها را بهصورت جداگانه ترکیب کرد. اگر یک عنصر در صفحه نمایان شود، همه عناصر مربوط به آن مورد باید ترکیببندی و اندازهگیری شوند. اگر بیشازحد استفاده شود، میتواند به عملکرد آسیب برساند. در حالت افراطی قرار دادن همه عناصر در یک مورد،
هدف استفاده از چیدمانهای Lazy را کاملاً ازبین میبرد. علاوهبر مشکلات عملکردی احتمالی، قرار دادن عناصر بیشتر در یک مورد با scrollToItem() و animateScrollToItem() نیز تداخل خواهد داشت.
بااینحال، موارد استفاده معتبری برای قرار دادن چندین عنصر در یک مورد وجود دارد، مانند داشتن تقسیمکنندهها در داخل یک فهرست. نمیخواهید جداکنندهها نمایههای پیمایش را تغییر دهند، زیرا نباید بهعنوان عناصر مستقل درنظر گرفته شوند. همچنین، عملکرد تحت تأثیر قرار نمیگیرد زیرا جداکنندهها کوچک هستند. احتمالاً وقتی مورد قبلاز جداکننده نمایان است، جداکننده هم باید نمایان باشد، بنابراین میتواند بخشی از مورد قبلی باشد:
LazyVerticalGrid( columns = GridCells.Adaptive(100.dp) ) { item { Item(0) } item { Item(1) Divider() } item { Item(2) } // ... }
استفاده از تنظیمات سفارشی را درنظر بگیرید
معمولاً فهرستهای Lazy موارد زیادی دارند و فضایی بیشتر از اندازه محفظه پیمایش را اشغال میکنند. بااینحال، وقتی فهرست شما با چند مورد پر میشود، طراحی شما میتواند الزامات خاصتری برای نحوه قرارگیری این موارد در نمای دید داشته باشد.
برای دستیابی به این هدف، میتوانید از عمودی سفارشی
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 کمک میکند.
سنجش عملکرد
فقط زمانی میتوانید عملکرد «چیدمان تنبل» را بهطور قابلاطمینان اندازهگیری کنید که در حالت انتشار اجرا شود و بهینهسازی R8 فعال باشد. در ساختهای اشکالزدایی، پیمایش چیدمان Lazy ممکن است کندتر بهنظر برسد. برای اطلاعات بیشتر درباره این موضوع، عملکرد Compose را بخوانید.
منابع بیشتر
- ایجاد فهرست پیمایششدنی محدود
- ایجاد شبکه پیمایشی
- نمایش موارد پیمایش تودرتو در فهرست
- فیلتر کردن فهرست درحین تایپ کردن
- بار کردن کُند دادهها با فهرستها و صفحهبندی
- ساختن فهرست بااستفاده از چند نوع مورد
- ویدیو: فهرستها در Compose
توصیهشده برای شما
- توجه: وقتی جاوا اسکریپت خاموش باشد، نوشتار پیوند نمایش داده میشود
- انتقال
RecyclerViewبه Lazy list - ذخیره کردن وضعیت میانای کاربر در Compose
- Kotlin برای Jetpack Compose