بسیاری از برنامه ها نیاز به نمایش مجموعه ای از موارد دارند. این سند توضیح می دهد که چگونه می توانید این کار را به طور موثر در 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 متفاوت است. اجزای Lazy به جای پذیرش یک پارامتر بلوک محتوای @Composable ، که به برنامهها اجازه میدهد مستقیماً composableها را منتشر کنند، یک بلوک 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 از نمایش آیتمها در یک شبکه پشتیبانی میکنند. یک شبکه عمودی تنبل موارد خود را در یک محفظه قابل پیمایش عمودی، که در چندین ستون قرار دارد، نمایش میدهد، در حالی که شبکههای افقی تنبل همان رفتار را در محور افقی خواهند داشت.
گریدها دارای همان قابلیت های 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 و items های LazyGridScope DSL مشخص کنید. maxLineSpan ، یکی از مقادیر span scope، به ویژه هنگامی که از اندازه تطبیقی استفاده می کنید مفید است، زیرا تعداد ستون ها ثابت نیست. این مثال نحوه ارائه یک فاصله ردیف کامل را نشان می دهد:
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 (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 از padding را به لبه های افقی (چپ و راست) و سپس 8.dp به بالا و پایین محتوا اضافه می کنیم.
لطفاً توجه داشته باشید که این بالشتک برای محتوا اعمال می شود، نه برای خود LazyColumn . در مثال بالا، اولین آیتم دارای بالشتک 8.dp به بالای آن، آخرین مورد 8.dp به پایین خود اضافه میکند و همه موارد دارای 16.dp در سمت چپ و راست خواهند بود.
به عنوان مثال دیگر، میتوانید Scaffold 's PaddingValues به 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 از انواعی مانند primitives، enums یا Parcelables پشتیبانی می کند.
LazyColumn { items(books, key = { // primitives, enums, Parcelable, etc. }) { // ... } }
کلید باید توسط Bundle پشتیبانی شود تا زمانی که Activity دوباره ایجاد میشود، یا حتی زمانی که از این مورد فاصله میگیرید و به عقب پیمایش میکنید، 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یک کلید منحصر به فرد را به هر مورد در لیست اختصاص می دهد. 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دکمه های مرتب سازی مجدد لیست را نمایش می دهد. توابع لامبدا را برای بازنشانی ترتیب و مرتبسازی فهرست بر اساس طول یا حروف الفبا دریافت میکند. -
ListAnimatedItemsListAnimatedItemsرا میخواند و فهرستdataرا برای نمایش لیست متحرک رشتهها ارسال میکند.dataدر جای دیگری تعریف شده است.
این قطعه یک رابط کاربری با دکمه های Add Item و Delete Item ایجاد می کند:
@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) } } } } }
نکات کلیدی در مورد کد
-
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 و جدیدتر از طریق کتابخانه androidx.paging:paging-compose پشتیبانی Compose را ارائه میکند.
برای نمایش لیستی از محتوای صفحهشده، میتوانیم از تابع پسوند collectAsLazyPagingItems() استفاده کنیم و سپس LazyPagingItems بازگشتی را به items() در LazyColumn خود منتقل کنیم. مشابه پشتیبانی صفحهبندی در نماها، میتوانید در حین بارگیری دادهها، با بررسی null بودن item ، متغیرهایی را نمایش دهید:
@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 پیکسل خودداری کنید
این ممکن است در سناریوهایی اتفاق بیفتد که، برای مثال، انتظار دارید به طور ناهمزمان برخی از دادهها مانند تصاویر را بازیابی کنید تا موارد فهرست خود را در مرحله بعد پر کنید. این باعث میشود که طرح تنبل تمام موارد خود را در اولین اندازهگیری ترکیب کند، زیرا ارتفاع آنها 0 پیکسل است و میتواند همه آنها را در نمای دید قرار دهد. هنگامی که موارد بارگیری شدند و ارتفاع آنها افزایش یافت، طرحبندیهای تنبل همه موارد دیگری را که برای اولین بار بهطور غیرضروری نوشته شدهاند، کنار میگذارند، زیرا در واقع نمیتوانند با درگاه دید مطابقت داشته باشند. برای جلوگیری از این امر، باید اندازه پیشفرض را روی آیتمهای خود تنظیم کنید تا طرحبندی 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) ) { // ... } }
مراقب قرار دادن چندین عنصر در یک آیتم باشید
در این مثال، آیتم دوم لامبدا 2 مورد را در یک بلوک منتشر می کند:
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) } // ... }
استفاده از ترتیبات سفارشی را در نظر بگیرید
معمولاً لیست های تنبل آیتم های زیادی دارند و بیش از اندازه ظرف پیمایش را اشغال می کنند. با این حال، زمانی که لیست شما با آیتم های کمی پر شده است، طراحی شما می تواند الزامات خاص تری برای نحوه قرارگیری این موارد در ویوپورت داشته باشد.
برای رسیدن به این هدف، می توانید از 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 Lazy خود، contentType به لیست ها یا شبکه های خود اضافه کنید. این به شما امکان میدهد نوع محتوا را برای هر مورد از طرحبندی مشخص کنید، در مواردی که فهرست یا شبکهای متشکل از چندین نوع مختلف از آیتمها را میسازید:
LazyColumn { items(elements, contentType = { it.type }) { // ... } }
هنگامی که contentType ارائه میکنید، Compose میتواند از ترکیببندیها فقط بین موارد از همان نوع استفاده مجدد کند. از آنجایی که استفاده مجدد هنگام نوشتن موارد با ساختار مشابه کارآمدتر است، ارائه انواع محتوا تضمین میکند که Compose سعی نمیکند یک مورد از نوع A را روی یک مورد کاملاً متفاوت از نوع B بنویسد. این به حداکثر رساندن مزایای استفاده مجدد از ترکیب و عملکرد Lazy Lazy شما کمک میکند.
اندازه گیری عملکرد
شما فقط میتوانید عملکرد یک طرحبندی Lazy را هنگام اجرا در حالت انتشار و با فعال بودن بهینهسازی R8 بهطور قابل اعتماد اندازهگیری کنید. در ساختهای اشکالزدایی، پیمایش طرحبندی تنبل ممکن است کندتر به نظر برسد. برای اطلاعات بیشتر در این مورد، Compose performance را بخوانید.
منابع اضافی
- یک لیست قابل پیمایش محدود ایجاد کنید
- یک شبکه قابل پیمایش ایجاد کنید
- نمایش موارد پیمایش تو در تو در یک لیست
- هنگام تایپ لیست را فیلتر کنید
- با تنبلی داده ها را با لیست ها و صفحه بندی بارگیری کنید
- با استفاده از چندین نوع آیتم فهرستی بسازید
- ویدئو: فهرستها در نوشتن
برای شما توصیه می شود
- توجه: وقتی جاوا اسکریپت خاموش است، متن پیوند نمایش داده می شود
-
RecyclerViewبه لیست Lazy منتقل کنید - حالت رابط کاربری را در Compose ذخیره کنید
- Kotlin برای Jetpack Compose