רשימות ורשתות

אפליקציות רבות צריכות להציג אוספים של פריטים. במסמך הזה מוסבר איך יכול לעשות זאת ביעילות ב-Jetpack פיתוח נייטיב.

אם ידוע לך שהתרחיש לדוגמה שלך לא דורש גלילה, תוכל משתמשים Column או Row פשוטים (בהתאם לכיוון), ופולטים את התוכן של כל פריט באמצעות חזרה על רשימה מסוימת באופן הבא:

@Composable
fun MessageList(messages: List<Message>) {
    Column {
        messages.forEach { message ->
            MessageRow(message)
        }
    }
}

נוכל לגרום לגלילה Column באמצעות הצירוף verticalScroll().

רשימות עצלות

אם אתם צריכים להציג מספר גדול של פריטים (או רשימה באורך לא ידוע), פריסה כמו Column יכולה לגרום לבעיות בביצועים, כי כל הפריטים יורכבו ויוצגו אם הם גלויים או לא.

הרכיב 'פיתוח נייטיב' מספק קבוצה של רכיבים שמרכיבים ופורסים רק פריטים גלויות באזור התצוגה של הרכיב. הרכיבים האלה כוללים LazyColumn וגם LazyRow

כפי שהשם מרמז, ההבדל בין LazyColumn וגם LazyRow הוא הכיוון שבו הם פורסים את הפריטים וגוללים. LazyColumn יוצרת רשימת גלילה אנכית, ו-LazyRow יוצרת רשימה אופקית רשימה של גלילה.

הרכיבים העצמיים שונים מרוב הפריסות בכלי 'פיתוח נייטיב'. במקום קבלת הפרמטר @Composable של חסימת תוכן, שמאפשרת לאפליקציות ישירות פולטים תכנים קומפוזביליים, רכיבי Lazy מספקים בלוק LazyListScope.(). הזה LazyListScope בלוק DSL שמאפשר לאפליקציות לתאר את תוכן הפריט. לאחר מכן, רכיב המעקב אחראי להוספת התוכן של כל פריט בתור בהתאם לפריסה ולמיקום הגלילה.

DSL: LazyListScope

ה-DSL של LazyListScope מספק כמה פונקציות לתיאור פריטים בפריסה. ברמה הבסיסית ביותר, item() מוסיף פריט יחיד, items(Int) מוסיף פריטים מרובים:

LazyColumn {
    // Add a single item
    item {
        Text(text = "First item")
    }

    // Add 5 items
    items(5) { index ->
        Text(text = "Item: $index")
    }

    // Add another single item
    item {
        Text(text = "Last item")
    }
}

יש גם כמה פונקציות של תוספים שמאפשרות להוסיף אוספים של פריטים, כמו List. התוספים האלה מאפשרים לנו להעביר את הדוגמה Column מלמעלה:

/**
 * import androidx.compose.foundation.lazy.items
 */
LazyColumn {
    items(messages) { message ->
        MessageRow(message)
    }
}

יש גם וריאציה של items() נקראת פונקציית התוסף itemsIndexed(), שמספק את האינדקס. כדאי לעיין LazyListScope לקבלת פרטים נוספים.

רשתות עצלות

LazyVerticalGrid וגם LazyHorizontalGrid תכנים קומפוזביליים מספקים תמיכה בהצגת פריטים ברשת. רשת אנכית מדורגת תציג את הפריטים בתוך מאגר שניתן לגלול בו לאורך, לרוחב עמודות רבות, בעוד שהרשתות האופקיות 'לאזיות' יפעלו באופן זהה על הציר האופקי.

לרשתות יש את אותן יכולות API עוצמתיות כמו לרשימות, והן גם משתמשות DSL דומה מאוד - LazyGridScope.() לתיאור התוכן.

צילום מסך של טלפון שבו מוצגת רשת של תמונות

הפרמטר columns בתוך LazyVerticalGrid ו-rows ב- LazyHorizontalGrid לקבוע איך תאים נוצרים בעמודות או שורות. הבאים מציגה פריטים ברשת, באמצעות GridCells.Adaptive כדי להגדיר כל עמודה ברוחב של 128.dp לפחות:

LazyVerticalGrid(
    columns = GridCells.Adaptive(minSize = 128.dp)
) {
    items(photos) { photo ->
        PhotoItem(photo)
    }
}

LazyVerticalGrid מאפשרת לציין רוחב של פריטים, ואז הרשת להתאים כמה שיותר עמודות. כל הרוחב שנותר מחולק באופן שווה בין העמודות, לאחר חישוב מספר העמודות. הדרך הזו לשינוי גודל שימושית במיוחד להצגת קבוצות של פריטים במסכים בגדלים שונים.

אם אתם יודעים את המספר המדויק של העמודות שבהן צריך להשתמש, אפשר במקום זאת לציין מופע של GridCells.Fixed שמכיל את מספר העמודות הנדרשות.

אם העיצוב דורש שרק פריטים מסוימים יהיו במידות לא סטנדרטיות, אפשר להשתמש בתמיכה ברשת כדי לספק עמודות מותאמות אישית לפריטים. צריך לציין את טווח העמודה עם הפרמטר span של LazyGridScope DSL item ו-items. maxLineSpan אחד מהערכים של היקף ה-span, הוא שימושי במיוחד כשמשתמשים שינוי גודל מותאם, כי מספר העמודות לא קבוע בדוגמה הזו מוסבר איך לציין טווח שורה מלא:

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.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()
)
רשת זזה של תמונות מדורגת ב&#39;פיתוח&#39;
איור 2. דוגמה לרשת אנכית מדורגת עם עמודות קבועות

המרווח הפנימי של התוכן

לפעמים צריך להוסיף מרווח פנימי סביב שולי התוכן. העצלנים מאפשרים להעביר 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 תומך בסוגים כמו פרימיטיביים, טיפוסים בני מנייה (enum) או למגרשים.

LazyColumn {
    items(books, key = {
        // primitives, enums, Parcelable, etc.
    }) {
        // ...
    }
}

המפתח חייב להיות נתמך על ידי Bundle כדי שה-rememberSaveable שבפנים אפשר לשחזר את התוכן הקומפוזבילי אחרי שחזור הפעילות, או אפילו כשגוללים אל מחוץ לפריט הזה וגוללים אחורה.

LazyColumn {
    items(books, key = { it.id }) {
        val rememberedValue = rememberSaveable {
            Random.nextInt()
        }
    }
}

אנימציות של פריטים

אם השתמשתם בווידג'ט של RecyclerView, סימן שהוא מכיל אנימציה של הפריט משתנה באופן אוטומטי. פריסות עצלות מספקות את אותה פונקציונליות לסידור מחדש של פריטים. ה-API פשוט – צריך רק להגדיר animateItemPlacement מגביל לתוכן הפריט:

LazyColumn {
    items(books, key = { it.id }) {
        Row(Modifier.animateItemPlacement()) {
            // ...
        }
    }
}

אפשר גם לספק מפרט אנימציה מותאם אישית, אם אתם צריכים:

LazyColumn {
    items(books, key = { it.id }) {
        Row(
            Modifier.animateItemPlacement(
                tween(durationMillis = 250)
            )
        ) {
            // ...
        }
    }
}

חשוב לספק מפתחות לפריטים כדי שיהיה אפשר למצוא את הפריט החדש המיקום של הרכיב שהועבר.

מלבד שינוי הסדר, אנימציות של פריטים משמשות כרגע להוספות ולהסרות בפיתוח. אפשר לעקוב אחרי ההתקדמות בקטע בעיה 150812265.

כותרות במיקום קבוע (ניסיוני)

הדפוס 'כותרת במיקום קבוע' מועיל בהצגת רשימות של נתונים מקובצים. למטה אפשר לראות דוגמה ל'רשימת אנשי קשר' המקובצת לפי ראשוני:

סרטון שבו מוצג טלפון שגולל למעלה ולמטה ברשימת אנשי הקשר

כדי ליצור כותרת במיקום קבוע באמצעות 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 נכסים.

אם נשתמש בדוגמה של הצגה והסתרה של לחצן על סמך מצב שבו המשתמש גלל ועובר את הפריט הראשון:

@OptIn(ExperimentalAnimationApi::class)
@Composable
fun MessageList(messages: List<Message>) {
    Box {
        val listState = rememberLazyListState()

        LazyColumn(state = listState) {
            // ...
        }

        // Show the button if the first visible item is past
        // the first item. We use a remembered derived state to
        // minimize unnecessary compositions
        val showButton by remember {
            derivedStateOf {
                listState.firstVisibleItemIndex > 0
            }
        }

        AnimatedVisibility(visible = showButton) {
            ScrollToTopButton()
        }
    }
}

קריאת המצב ישירות במצב היצירה שימושית כשצריך לבצע עדכון תכנים קומפוזביליים אחרים בממשק המשתמש, אבל יש גם תרחישים שבהם האירוע לא צריך שצריך לטפל בהם באותה הרכבה. דוגמה נפוצה לכך היא שליחת אירוע ב-Analytics אחרי שהמשתמש גלל ומעבר לנקודה מסוימת. כדי לטפל בזה בצורה יעילה, אנחנו יכולים להשתמש snapshotFlow():

val listState = rememberLazyListState()

LazyColumn(state = listState) {
    // ...
}

LaunchedEffect(listState) {
    snapshotFlow { listState.firstVisibleItemIndex }
        .map { index -> index > 0 }
        .distinctUntilChanged()
        .filter { it }
        .collect {
            MyAnalyticsService.sendScrolledPastFirstItemEvent()
        }
}

LazyListState מספק גם מידע על כל הפריטים שנמצאים כרגע על המסך, ועל הגבולות שלהם, layoutInfo לנכס. לצפייה LazyListLayoutInfo למידע נוסף.

שליטה במיקום הגלילה

בנוסף לתגובה למיקום הגלילה, כדאי גם שאפליקציות יכולות לשלוט גם במיקום הגלילה. LazyListState תומך בכך דרך scrollToItem() היא שמקליטה באופן מיידי מעבר למיקום הגלילה, וגם animateScrollToItem() שגוללת באמצעות אנימציה (שנקראת גם גלילה חלקה):

@Composable
fun MessageList(messages: List<Message>) {
    val listState = rememberLazyListState()
    // Remember a CoroutineScope to be able to launch
    val coroutineScope = rememberCoroutineScope()

    LazyColumn(state = listState) {
        // ...
    }

    ScrollToTopButton(
        onClick = {
            coroutineScope.launch {
                // Animate scroll to the first item
                listState.animateScrollToItem(index = 0)
            }
        }
    )
}

מערכי נתונים גדולים (חלוקה לדפים)

ספריית הדפים מאפשרת לאפליקציות לתמוך ברשימות גדולות של פריטים, לטעון ולהציג מקטעי נתונים קטנים של הרשימה הנחוצים. Paging 3.0 ואילך מספק תמיכה ב-Compose באמצעות הספרייה androidx.paging:paging-compose.

כדי להציג רשימה של התוכן בדפים, אפשר להשתמש collectAsLazyPagingItems() של פונקציית הרחבה, ואז מעבירים את הפונקציה LazyPagingItems אל items() ב-LazyColumn. בדומה לתמיכה בהחלפה של תצוגות מפורטות, אפשר כדי להציג placeholders בזמן טעינת הנתונים, צריך לבדוק אם הערך של 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
        // ...
    )
}

אם אתם יודעים מה הגודל המשוער של הפריטים אחרי שהנתונים נטענות באופן אסינכרוני, מומלץ לוודא שהמידות של הפריטים יישארו זהים לפני הטעינה ואחריה, לדוגמה, על ידי הוספת כמה placeholders. כך תוכלו לשמור על מיקום הגלילה הנכון.

אין להציב רכיבים שיש בהם גלילה באותו כיוון

הדבר רלוונטי רק למקרים שבהם מקוננים ילדים שניתן לגלול ללא הגדרה מראש גודל בתוך תבנית הורה אחרת עם גלילה באותו כיוון. לדוגמה, בניסיון להציב ילד 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 פולט 2 פריטים בבלוק אחד:

LazyVerticalGrid(
    columns = GridCells.Adaptive(100.dp)
) {
    item { Item(0) }
    item {
        Item(1)
        Item(2)
    }
    item { Item(3) }
    // ...
}

פריסות עצלות יטפלו בזה כצפוי – הן יתפרסו את הרכיב הראשון אחרי שיחה אחרת, כאילו היו פריטים שונים. עם זאת, יש לאיות בעיות.

כשנשלחים כמה רכיבים כחלק מפריט אחד, ההתייחסות אליהם היא בתור ישות אחת, כלומר כבר לא ניתן להרכיב אותה בנפרד. אם רוצים הופך לגלוי במסך, ואז כל הרכיבים התואמים צריך להרכיב ולמדוד. השימוש בה עשוי לפגוע בביצועים באופן מוגזם. במקרה הקיצוני של הכנסת כל הרכיבים לפריט אחד, מבטלת לחלוטין את המטרה של שימוש בפריסות Lazy. מלבד הפוטנציאל בעיות בביצועים. גם הוספה של יותר רכיבים לפריט אחד תפריע להם. עם scrollToItem() ועם animateScrollToItem().

עם זאת, יש תרחישים לדוגמה תקינים להצבת רכיבים מרובים בפריט אחד, כמו להציב מחיצות בתוך רשימה. אינך רוצה שהקווים יגרמו לשינוי הגלילה של מדדים, כי הם לא אמורים להיחשב כרכיבים עצמאיים. כמו כן, ביצועים לא יושפעו, מכיוון שהמפרידים קטנים. סביר להניח שיהיה צורך במחיצה גלויים כשהפריט לפניו גלוי, כך שהם יכולים להיות חלק מהפריט הקודם item:

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, כדי למקסם את הביצועים של התכונה פריסה, כדאי להוסיף contentType לרשימות או לתצוגות רשת. כך אפשר לציין את סוג התוכן של כל סוג פריט בפריסה, במקרים שבהם כותבים רשימה או רשת של כמה סוגים שונים של פריטים:

LazyColumn {
    items(elements, contentType = { it.type }) {
        // ...
    }
}

כשמציינים contentType אפשר להשתמש בניסוח האוטומטי רק ביצירות מוזיקליות בין פריטים מאותו סוג. שימוש חוזר יעיל יותר הרכב פריטים בעלי מבנה דומה, ולספק את סוגי התוכן כדי להבטיח התכונה 'פיתוח נייטיב' לא מנסה להרכיב פריט מסוג A בנוסף פריט שונה מסוג B. כך אפשר למקסם את היתרונות של הרכבה שימוש חוזר וביצועי פריסה עילית.

מדידת ביצועים

אפשר למדוד בצורה אמינה את הביצועים של פריסה מושהית רק בזמן הרצה במצב הפצה ועם אופטימיזציית R8 מופעלת. בגרסאות build של ניפוי באגים, פריסה מדורגת ייתכן שהגלילה תופיע לאט יותר. כדי לקבל מידע נוסף בנושא, אפשר לעיין ביצועי הכתיבה.