تحتاج العديد من التطبيقات إلى عرض مجموعات من العناصر. يشرح هذا المستند كيفية تنفيذ ذلك بكفاءة في Jetpack Compose.
إذا كنت تعلم أنّ حالة الاستخدام لا تتطلّب الانتقال إلى أعلى أو أسفل الصفحة، يمكنك
استخدام Column
أو Row
(حسب الاتجاه) وعرض محتوى كل عنصر من خلال
التنقّل في قائمة بالطريقة التالية:
@Composable fun MessageList(messages: List<Message>) { Column { messages.forEach { message -> MessageRow(message) } } }
يمكننا جعل Column
قابلاً للتنقّل باستخدام المُعدِّل verticalScroll()
.
القوائم غير الكاملة
إذا كنت بحاجة إلى عرض عدد كبير من العناصر (أو قائمة بطول غير معروف)،
يمكن أن يؤدي استخدام تنسيق مثل Column
إلى حدوث مشاكل في الأداء، لأنّه سيتم إنشاء جميع العناصر وعرضها سواء كانت مرئية أم لا.
توفّر أداة "الإنشاء" مجموعة من المكوّنات التي تُنشئ وتُرتّب العناصر التي
تكون مرئية في إطار عرض المكوّن فقط. وتشمل هذه المكوّنات
LazyColumn
و
LazyRow
.
كما يشير الاسم، يتمثل الفرق بين
LazyColumn
و
LazyRow
في الاتجاه الذي يتم فيه عرض العناصر والانتقال فيها. يعرض الرمز LazyColumn
قائمة قابلة للانتقال عموديًا، ويعرض الرمز LazyRow
قائمة قابلة للانتقال أفقيًا.
تختلف المكونات غير المُشغَّلة عن معظم التنسيقات في ميزة "الإنشاء". بدلاً من
قبول مَعلمة @Composable
لوحدة محتوى، ما يسمح للتطبيقات ببث العناصر القابلة للتجميع مباشرةً، تقدّم المكونات البطيئة وحدة LazyListScope.()
. يوفّر هذا العنصر
LazyListScope
لغة وصفية تتيح للتطبيقات وصف محتوى السلع. بعد ذلك، يتحمّل العنصر المتغيّر مسؤولية إضافة محتوى كل عنصر على النحو المطلوب وفقًا للتنسيق ووضع الانتقال.
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
إمكانية عرض العناصر في شبكة. ستعرِض الشبكة العمودية المتغيّرة
عناصرها في حاوية قابلة للتنقّل عموديًا، على مستوى
أعمدة متعدّدة، في حين ستتّبع الشبكات الأفقية المتغيّرة السلوك نفسه
على المحور الأفقي.
تتمتع الشبكات بإمكانات واجهة برمجة التطبيقات القوية نفسها التي تتمتع بها القوائم، كما تستخدم أيضًا
لغة وصفية مشابهة جدًا،
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
على يمينها ويسارها.
المسافة بين المحتوى
لإضافة مسافات بين العناصر، يمكنك استخدام
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) } }
من خلال تقديم مفاتيح، يمكنك مساعدة ميزة "الإنشاء" في معالجة عمليات إعادة الترتيب بشكل صحيح. على سبيل المثال، إذا كان العنصر يحتوي على حالة محفوظة، ستسمح مفاتيح الإعدادات لميزة الإنشاء بنقل هذه الحالة مع العنصر عند تغيير موضعه.
LazyColumn { items(books, key = { it.id }) { val rememberedValue = remember { Random.nextInt() } } }
ومع ذلك، هناك قيد واحد على الأنواع التي يمكنك استخدامها كمفاتيح سلع.
يجب أن يكون نوع المفتاح متوافقًا مع
Bundle
، وهي آلية Android للحفاظ على
الحالات عند إعادة إنشاء النشاط. يتوافق Bundle
مع أنواع مثل العناصر الأساسية أو
القوائم أو Parcelables.
LazyColumn { items(books, key = { // primitives, enums, Parcelable, etc. }) { // ... } }
يجب أن يكون المفتاح متوافقًا مع Bundle
حتى يمكن استعادة rememberSaveable
داخل
العنصر القابل للتجميع عند إعادة إنشاء النشاط، أو حتى
عند الانتقال بعيدًا عن هذا العنصر والرجوع إليه.
LazyColumn { items(books, key = { it.id }) { val rememberedValue = rememberSaveable { Random.nextInt() } } }
الصور المتحركة للعناصر
إذا كنت قد استخدمت تطبيق RecyclerView المصغّر، ستلاحظ أنّه يضيف تأثيرًا متحركًا
للتغييرات تلقائيًا.
توفّر التنسيقات المُهمّلة الوظيفة نفسها لإعادة ترتيب العناصر.
واجهة برمجة التطبيقات بسيطة، ما عليك سوى ضبط المُعدِّل
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) ) ) { // ... } } }
احرص على تقديم مفاتيح للعناصر لكي يكون من الممكن العثور على الترتيب الجديد للعنصر الذي تم نقله.
العناوين الثابتة (ميزة تجريبية)
يكون نمط "العنوان الثابت" مفيدًا عند عرض قوائم بالبيانات المجمّعة. يمكنك الاطّلاع أدناه على مثال على "قائمة جهات اتصال"، مجمّعة حسب الحرف الأول من اسم كل جهة اتصال:
لإنشاء عنوان ثابت باستخدام 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 Load حالة الاستخدام هذه من خلال رفع العنصر
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) } } ) }
مجموعات البيانات الكبيرة (الفهرسة)
تتيح مكتبة الفهرسة للتطبيقات استخدام
قوائم كبيرة من العناصر، وتحميل أجزاء صغيرة من القائمة وعرضها عند الضرورة. توفّر ميزة "التنقّل بين الصفحات" 3.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() } } } }
نصائح حول استخدام التنسيقات المتغيّرة
هناك بعض النصائح التي يمكنك أخذها في الاعتبار لضمان عمل التنسيقات المُعدّة للتحميل البطيء على النحو المطلوب.
تجنَّب استخدام عناصر بحجم 0 بكسل.
يمكن أن يحدث ذلك في سيناريوهات تتوقّع فيها مثلاً استرداد بعض البيانات بشكل غير متزامن، مثل الصور، لملء عناصر قائمتك في مرحلة لاحقة. سيؤدي ذلك إلى أن يُنشئ التخطيط المُتأخّر جميع عناصره في القياس الأول، لأنّ ارتفاعها 0 بكسل ويمكن أن تلائمها جميعًا في إطار العرض. بعد تحميل العناصر وتوسيع ارتفاعها، ستتجاهل التنسيقات المُعدّة للتحميل في وقت لاحق جميع العناصر الأخرى التي تم إنشاؤها بدون داعٍ في المرة الأولى لأنّها لا يمكنها في الواقع أن تلائم إطار العرض. لتجنُّب ذلك، عليك ضبط الحجم التلقائي للعناصر، حتى يتمكّن التصميم المُتأخّر من إجراء الحسابات الصحيحة لعدد العناصر التي يمكن أن تلائم إطار العرض:
@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) ) { // ... } }
احرِص على عدم وضع عناصر متعدّدة في عنصر واحد.
في هذا المثال، تُصدِر دالة lambda للعنصر الثاني عنصرَين في كتلة واحدة:
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
.
بدءًا من الإصدار 1.2 من Compose، لتحسين أداء التنسيق المتغيّر ببطء، ننصحك بإضافة contentType
إلى القوائم أو الشبكات. يتيح لك ذلك تحديد نوع المحتوى لكل
عنصر من عناصر التنسيق، في الحالات التي تنشئ فيها قائمة أو شبكة تتألف
من أنواع متعددة مختلفة من العناصر:
LazyColumn { items(elements, contentType = { it.type }) { // ... } }
عند تقديم
contentType
،
يمكن لأداة "الإنشاء" إعادة استخدام التركيبات فقط
بين العناصر من النوع نفسه. بما أنّ إعادة الاستخدام أكثر فعالية عند
إنشاء عناصر ذات بنية مشابهة، فإنّ توفير أنواع المحتوى يضمن عدم محاولة أداة
الإنشاء إنشاء عنصر من النوع "أ" فوق عنصر مختلف تمامًا من النوع "ب". يساعد ذلك في زيادة مزايا إعادة استخدام العناصر المكوّنة
وتحسين أداء التنسيق المُتأخّر.
قياس الأداء
لا يمكنك قياس أداء التنسيق المُتأخّر إلا بشكل موثوق عند التشغيل في وضع الإصدار وتفعيل تحسين R8. في عمليات إنشاء تصحيح الأخطاء، قد يبدو التنقّل في ميزة "العرض البطيء" أبطأ. لمزيد من المعلومات حول هذا الموضوع، يُرجى الاطّلاع على مقالة أداء ميزة "الإنشاء".
أفلام مُقترَحة لك
- ملاحظة: يتم عرض نص الرابط عندما تكون لغة JavaScript غير مفعّلة.
- نقل
RecyclerView
إلى قائمة "العرض غير التلقائي" - حفظ حالة واجهة المستخدم في ميزة "الإنشاء"
- Kotlin لـ Jetpack Compose