فهرست ها و شبکه ها

بسیاری از برنامه ها نیاز به نمایش مجموعه ای از آیتم ها دارند. این سند توضیح می دهد که چگونه می توانید این کار را به طور موثر در 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()
)

شکل 1. مثالی از شبکه عمودی تنبل

برای تنظیم تعداد ثابتی از ستون‌ها، می‌توانید به جای StaggeredGridCells.Adaptive از 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()
)
Lazy staggered grid of images in Compose
شکل 2. نمونه ای از شبکه عمودی تنبل با ستون های ثابت

بالشتک محتوا

گاهی اوقات شما نیاز به اضافه کردن بالشتک در لبه های محتوا دارید. کامپوننت‌های تنبل به شما اجازه می‌دهند تا برخی از PaddingValues به پارامتر contentPadding ارسال کنید تا از این امر پشتیبانی کند:

LazyColumn(
    contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
) {
    // ...
}

در این مثال، ما 16.dp از padding را به لبه های افقی (چپ و راست) و سپس 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)
    }
}

با ارائه کلیدها، به 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 ساده است - فقط باید اصلاح کننده animateItemPlacement را روی محتوای آیتم تنظیم کنید:

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 با بالا بردن 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 را هنگام اجرا در حالت انتشار و با فعال بودن بهینه‌سازی R8 به‌طور قابل اعتماد اندازه‌گیری کنید. در ساخت‌های اشکال‌زدایی، پیمایش طرح‌بندی تنبل ممکن است کندتر به نظر برسد. برای اطلاعات بیشتر در این مورد، Compose performance را بخوانید.

{% کلمه به کلمه %} {% آخر کلمه %} {% کلمه به کلمه %} {% آخر کلمه %}