รายการและตารางกริด

แอปจำนวนมากจำเป็นต้องแสดงคอลเล็กชันรายการ เอกสารนี้จะอธิบายวิธีที่คุณ ก็ทำได้ใน Jetpack Compose

หากคุณทราบว่ากรณีการใช้งานของคุณไม่จำเป็นต้องมีการเลื่อนหน้าจอใดๆ คุณอาจต้องการ ใช้ Column หรือ Row แบบง่าย (ขึ้นอยู่กับทิศทาง) และแสดงเนื้อหาของแต่ละรายการตาม ทำซ้ำผ่านรายการในลักษณะต่อไปนี้

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

เราสามารถทำให้ Column เลื่อนได้โดยใช้ตัวปรับแต่ง verticalScroll()

รายการแบบ Lazy Loading

หากต้องการแสดงรายการจำนวนมาก (หรือรายการที่ไม่ทราบความยาว) การใช้เลย์เอาต์ เช่น Column อาจก่อให้เกิดปัญหาด้านประสิทธิภาพ เนื่องจากรายการทั้งหมดจะได้รับการเขียนและจัดวางในตำแหน่งว่าจะแสดงหรือไม่

Compose มีชุดคอมโพเนนต์ที่ใช้เขียนและจัดเลย์เอาต์เฉพาะรายการที่ มองเห็นได้ในวิวพอร์ตของคอมโพเนนต์ องค์ประกอบเหล่านี้ประกอบด้วย LazyColumn และ LazyRow

ความแตกต่างระหว่าง LazyColumn และ LazyRow คือการจัดวางสินค้าและการเลื่อน LazyColumn จะสร้างรายการแบบเลื่อนในแนวตั้ง และ LazyRow จะสร้างรายการแนวนอน รายการแบบเลื่อน

คอมโพเนนต์แบบ Lazy จะแตกต่างจากเลย์เอาต์ส่วนใหญ่ใน Compose แทนที่จะเป็น การยอมรับพารามิเตอร์บล็อกเนื้อหา @Composable ซึ่งทำให้แอป ปล่อย Composable ส่วนคอมโพเนนต์ Lazy จะมีบล็อก LazyListScope.() ช่วงเวลานี้ LazyListScope บล็อกมี DSL ซึ่งช่วยให้แอปอธิบายเนื้อหาในรายการได้ จากนั้นคอมโพเนนต์แบบ Lazy Loading จะเพิ่มเนื้อหาของแต่ละรายการเป็น ที่จำเป็นสำหรับเลย์เอาต์และตำแหน่งการเลื่อน

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 ข้อมูลอ้างอิงสำหรับรายละเอียดเพิ่มเติม

ตารางกริดแบบ Lazy Loading

LazyVerticalGrid และ LazyHorizontalGrid Composables รองรับการแสดงรายการในตารางกริด ตารางกริดแนวตั้งแบบ Lazy จะแสดงรายการต่างๆ ในภาชนะที่เลื่อนได้ในแนวตั้ง หลายคอลัมน์ ในขณะที่ตารางกริดแนวนอนแบบ Lazy ก็จะมีลักษณะการทำงานเหมือนกัน บนแกนแนวนอน

ตารางกริดมีความสามารถ 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")
    }
    // ...
}

ตารางกริดแบบ Lazy Loading

LazyVerticalStaggeredGrid และ LazyHorizontalStaggeredGrid เป็น Composable ที่ช่วยให้คุณสร้างตารางกริดที่จัดเรียงแบบ Lazy Loading และรายการได้ ตารางกริดแบบ Lazy Loading ในแนวตั้งที่แสดงสินค้าแบบเลื่อนได้ในแนวตั้ง ที่ครอบคลุมหลายคอลัมน์และทำให้แต่ละรายการสามารถ ความสูงที่ต่างกัน ตารางกริดแนวนอนแบบ Lazy Loading จะมีลักษณะแบบเดียวกันบน แกนแนวนอนที่มีรายการความกว้างต่างกัน

ตัวอย่างต่อไปนี้เป็นตัวอย่างพื้นฐานของการใช้ 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 ตัวอย่างตารางกริดแนวตั้งแบบ Lazy Loading

หากต้องการกำหนดจำนวนคอลัมน์คงที่ คุณสามารถใช้ 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()
)
ตารางกริดที่จัดเรียงแบบ Lazy Loading ของรูปภาพใน Compose
รูปที่ 2 ตัวอย่างตารางกริดแนวตั้งแบบ Lazy Loading ที่มีคอลัมน์แบบคงที่

ระยะห่างจากขอบของเนื้อหา

บางครั้ง คุณจะต้องเพิ่มระยะห่างจากขอบรอบขอบของเนื้อหา คนขี้เกียจ จะช่วยให้คุณสามารถส่ง 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)
    }
}

การระบุคีย์จะช่วยให้คุณ Compose ในการจัดการการจัดเรียงใหม่อย่างถูกต้อง ตัวอย่างเช่น หากรายการมีสถานะ "จำแล้ว" การตั้งค่าคีย์จะอนุญาตให้ เขียนเพื่อย้ายสถานะนี้ไปพร้อมกับรายการ เมื่อตำแหน่งเปลี่ยนไป

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

อย่างไรก็ตาม ประเภทที่คุณใช้เป็นคีย์รายการได้มีข้อจำกัด 1 ข้อ ประเภทคีย์ต้องได้รับการสนับสนุนโดย Bundle ซึ่งเป็นกลไกของ Android ที่ใช้เก็บ สถานะเมื่อมีการสร้างกิจกรรมอีกครั้ง Bundle รองรับประเภท เช่น ประเภทพื้นฐาน enum หรือ Parcelables

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

คีย์ต้องได้รับการรองรับโดย Bundle เพื่อให้ rememberSaveable ที่อยู่ข้างใน สามารถกู้คืนรายการ Composable ได้เมื่อมีการสร้างกิจกรรมใหม่ หรือ เมื่อคุณเลื่อนออกไปจาก รายการนี้และเลื่อนกลับ

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

ภาพเคลื่อนไหวของรายการ

หากใช้วิดเจ็ต RecyclerView คุณจะทราบได้ว่าวิดเจ็ตทำให้รายการเคลื่อนไหว โดยอัตโนมัติ เลย์เอาต์แบบ Lazy Loading มีฟังก์ชันแบบเดียวกันสําหรับการจัดเรียงสินค้าใหม่ 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()
        }
    }
}

การอ่านสถานะในการเขียนโดยตรงจะมีประโยชน์เมื่อคุณต้องการอัปเดต UI อื่นๆ ที่เป็น Composable อื่น แต่ก็มีบางสถานการณ์ที่ไม่จำเป็นต้อง ในการเรียบเรียงเดียวกัน ตัวอย่างทั่วไปของเหตุการณ์นี้คือการส่ง 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)
            }
        }
    )
}

ชุดข้อมูลขนาดใหญ่ (การแบ่งหน้า)

ไลบรารีการแบ่งหน้าช่วยให้แอปทำสิ่งต่อไปนี้ได้ รองรับรายการจำนวนมาก การโหลดและการแสดงรายการส่วนเล็กๆ เป็น ตามความจำเป็น หน้า 3.0 ขึ้นไปจะให้การสนับสนุน Compose ผ่าน คลัง androidx.paging:paging-compose

หากต้องการแสดงรายการเนื้อหาที่แบ่งหน้า เราสามารถใช้ collectAsLazyPagingItems() ของส่วนขยาย แล้วส่งผ่าน URL ที่แสดงผล 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 Loading

มีเคล็ดลับ 2-3 ข้อที่คุณนำมาพิจารณาเพื่อให้แน่ใจว่าเลย์เอาต์แบบ Lazy Loading จะทำงานได้ตามที่ต้องการ

หลีกเลี่ยงการใช้รายการขนาด 0 พิกเซล

ซึ่งอาจเกิดขึ้นได้ในบางสถานการณ์ เช่น คุณคาดว่าไม่พร้อมกัน ดึงข้อมูลบางอย่าง เช่น รูปภาพ เพื่อเติมให้กับรายการของคุณในขั้นตอนต่อไป ซึ่งจะทำให้เลย์เอาต์แบบ Lazy จัดองค์ประกอบทั้งหมดใน เนื่องจากความสูงเท่ากับ 0 พิกเซล และพอดีกับขนาดทั้งหมด วิวพอร์ต เมื่อรายการโหลดและขยายความสูงของรายการแล้ว เลย์เอาต์แบบ Lazy จะลบรายการอื่นๆ ทั้งหมดที่ไม่ได้เขียนขึ้นโดยไม่จำเป็น ครั้งแรกเพราะขนาดพอดีกับวิวพอร์ตจริงๆ เพื่อหลีกเลี่ยงปัญหานี้ คุณควรตั้งค่าขนาดเริ่มต้นให้กับสินค้าเพื่อให้เลย์เอาต์แบบ Lazy Loading ได้ การคำนวณที่ถูกต้องของจำนวนรายการที่พอดีกับวิวพอร์ต

@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 หลัก 1 ราย และใช้ 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 lambda จะแสดง 2 รายการใน 1 บล็อก

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

เลย์เอาต์แบบ Lazy Loading จะจัดการสิ่งนี้ตามที่คาดไว้ โดยจัดเลย์เอาต์องค์ประกอบ 1 รายการ ตามหลังอีกชิ้นหนึ่ง ราวกับว่าเป็นคนละรายการกัน อย่างไรก็ตาม มีคำแนะนำ ของปัญหาในการทำเช่นนั้น

เมื่อมีการปล่อยองค์ประกอบหลายองค์ประกอบออกมาเป็นส่วนหนึ่งของรายการเดียว องค์ประกอบเหล่านั้นจะมีการจัดการในฐานะ เอนทิตีเดียว ซึ่งหมายความว่าจะแต่งเพลงเหล่านั้นแยกกันไม่ได้อีก หากมี จะมองเห็นได้บนหน้าจอ จากนั้นองค์ประกอบทั้งหมดที่สอดคล้องกับ รายการจะต้องได้รับการแต่งขึ้นและวัดค่า ซึ่งอาจส่งผลเสียต่อประสิทธิภาพหากใช้ มากเกินไป ในกรณีสุดโต่งคือการใส่องค์ประกอบทั้งหมดไว้ในรายการเดียว ทำให้ไม่สามารถใช้เลย์เอาต์แบบ Lazy Loading ได้ นอกเหนือจากศักยภาพ ปัญหาด้านประสิทธิภาพ การเพิ่มองค์ประกอบอื่นๆ ในรายการเดียวก็จะเป็นอุปสรรคเช่นกัน กับ scrollToItem() และ animateScrollToItem()

อย่างไรก็ตาม มีกรณีการใช้งานที่ถูกต้องสำหรับการวางองค์ประกอบหลายรายการในรายการเดียว เช่น มีเส้นแบ่งอยู่ภายในรายการ คุณไม่ต้องการให้เส้นแบ่งเปลี่ยนการเลื่อน เนื่องจากไม่ควรถือเป็นองค์ประกอบอิสระ นอกจากนี้ ประสิทธิภาพ จะไม่ได้รับผลกระทบเพราะเส้นแบ่งมีขนาดเล็ก ตัวแบ่งอาจต้อง แสดงเมื่อรายการก่อนที่จะเห็น เพื่อให้เป็นส่วนหนึ่งของ รายการ:

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

ลองใช้การจัดเรียงที่กำหนดเอง

โดยปกติรายการแบบ Lazy Loading ปกติแล้วจะมีหลายรายการ และมีขนาดมากกว่า คอนเทนเนอร์แบบเลื่อนได้ อย่างไรก็ตาม เมื่อในรายการมีการป้อนข้อมูล 2-3 รายการ อาจมีข้อกำหนดที่เฉพาะเจาะจงมากขึ้น เกี่ยวกับวิธีวางตำแหน่งโฆษณา ในวิวพอร์ต

เพื่อให้บรรลุเป้าหมายนี้ คุณสามารถใช้ประเภทธุรกิจที่กำหนดเอง 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 การเขียนสามารถใช้การเรียบเรียงเพลงซ้ำเพียงอย่างเดียว ระหว่างรายการที่อยู่ในประเภทเดียวกันได้ เพราะการนำมาใช้ซ้ำจะมีประสิทธิภาพมากกว่าเมื่อคุณ เขียนรายการที่มีโครงสร้างคล้ายกัน ทำให้ประเภทเนื้อหามั่นใจได้ว่า การเขียนไม่ได้พยายามเขียนรายการประเภท A ซ้อนทับกับ รายการอื่นของประเภท B ซึ่งจะช่วยเพิ่มประโยชน์ขององค์ประกอบภาพได้สูงสุด การนำมาใช้ซ้ำและประสิทธิภาพของเลย์เอาต์แบบ Lazy Loading

การวัดประสิทธิภาพ

คุณจะวัดประสิทธิภาพของเลย์เอาต์แบบ Lazy Loading ได้อย่างน่าเชื่อถือเท่านั้นเมื่อเรียกใช้ใน โหมดเผยแพร่ และเปิดใช้การเพิ่มประสิทธิภาพ R8 ในบิลด์การแก้ไขข้อบกพร่อง เลย์เอาต์แบบ Lazy การเลื่อนอาจปรากฏขึ้นช้าลง สำหรับข้อมูลเพิ่มเติม โปรดอ่าน ประสิทธิภาพของการเขียน