แอปจํานวนมากจําเป็นต้องแสดงคอลเล็กชันรายการ เอกสารนี้อธิบายวิธีดำเนินการนี้อย่างมีประสิทธิภาพใน Jetpack Compose
หากทราบว่า Use Case ของคุณไม่จําเป็นต้องเลื่อน คุณอาจต้องใช้ Column
หรือ Row
แบบธรรมดา (ขึ้นอยู่กับทิศทาง) และแสดงเนื้อหาของแต่ละรายการโดยวนซ้ำรายการตามวิธีต่อไปนี้
@Composable fun MessageList(messages: List<Message>) { Column { messages.forEach { message -> MessageRow(message) } } }
เราทําให้ Column
เลื่อนได้โดยใช้ตัวแก้ไข verticalScroll()
รายการแบบ Lazy
หากต้องการแสดงรายการจํานวนมาก (หรือรายการที่มีความยาวที่ไม่รู้จัก) การใช้เลย์เอาต์ เช่น Column
อาจทําให้เกิดปัญหาด้านประสิทธิภาพ เนื่องจากระบบจะจัดวางและแสดงรายการทั้งหมดไม่ว่าจะมองเห็นหรือไม่ก็ตาม
คอมโพสิชันมีชุดคอมโพเนนต์ที่คอมโพสและวางเลย์เอาต์เฉพาะรายการที่มองเห็นได้ในวิวพอร์ตของคอมโพเนนต์ คอมโพเนนต์เหล่านี้ ได้แก่
LazyColumn
และ
LazyRow
ตามชื่อที่บอกไว้ ความแตกต่างระหว่าง LazyColumn
กับ LazyRow
คือการวางแนวของรายการและการเลื่อน LazyColumn
จะสร้างรายการแบบเลื่อนในแนวตั้ง และ LazyRow
จะสร้างรายการแบบเลื่อนในแนวนอน
คอมโพเนนต์แบบ Lazy แตกต่างจากเลย์เอาต์ส่วนใหญ่ใน Compose คอมโพเนนต์แบบเลื่อนออกจะแสดงบล็อก LazyListScope.()
แทนที่จะรับพารามิเตอร์บล็อกเนื้อหา @Composable
ซึ่งช่วยให้แอปแสดงคอมโพสิเบิลได้โดยตรง บล็อก 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
ตารางกริดแบบ Lazy
คอมโพสิเบิล LazyVerticalGrid
และ LazyHorizontalGrid
รองรับการแสดงรายการในตารางกริด ตารางกริดแนวตั้งแบบ 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
LazyVerticalStaggeredGrid
และ
LazyHorizontalStaggeredGrid
เป็นคอมโพสิชันที่ช่วยให้คุณสร้างตารางกริดของรายการแบบทีละรายการที่โหลดแบบ Lazy Load
ตารางกริดแนวตั้งแบบแสดงรายการทีละรายการแบบเลื่อนช้าจะแสดงรายการในคอนเทนเนอร์ที่เลื่อนขึ้นลงได้ซึ่งครอบคลุมหลายคอลัมน์และอนุญาตให้แต่ละรายการมีความสูงต่างกัน ตารางกริดแนวนอนแบบ Lazy จะทำงานในลักษณะเดียวกันบนแกนแนวนอนที่มีรายการที่มีความกว้างต่างกัน
ข้อมูลโค้ดต่อไปนี้เป็นตัวอย่างพื้นฐานของการใช้ 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() )
การเว้นวรรคเนื้อหา
บางครั้งคุณจะต้องเพิ่มระยะห่างจากขอบของเนื้อหา คอมโพเนนต์แบบ Lazy ช่วยให้คุณส่ง 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
รองรับประเภทต่างๆ เช่น ประเภทพื้นฐาน อนุกรม หรือ Parcelables
LazyColumn { items(books, key = { // primitives, enums, Parcelable, etc. }) { // ... } }
Bundle
ต้องรองรับคีย์ดังกล่าวเพื่อให้ระบบกู้คืน rememberSaveable
ภายในคอมโพสิเบิลของรายการได้เมื่อสร้างกิจกรรมขึ้นมาใหม่ หรือแม้กระทั่งเมื่อคุณเลื่อนออกจากรายการนี้และเลื่อนกลับ
LazyColumn { items(books, key = { it.id }) { val rememberedValue = rememberSaveable { Random.nextInt() } } }
ภาพเคลื่อนไหวของไอเทม
หากเคยใช้วิดเจ็ต RecyclerView คุณจะทราบว่าวิดเจ็ตดังกล่าวจะแสดงการเปลี่ยนแปลงของรายการเป็นภาพเคลื่อนไหวโดยอัตโนมัติ
เลย์เอาต์แบบ Lazy มีฟังก์ชันการทำงานเดียวกันสำหรับการเรียงลำดับรายการใหม่
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) ) ) { // ... } } }
ตรวจสอบว่าคุณระบุคีย์สำหรับรายการเพื่อให้ระบบค้นหาตำแหน่งใหม่ขององค์ประกอบที่ย้ายได้
ส่วนหัวแบบติดหนึบ (ทดลอง)
รูปแบบ "ส่วนหัวแบบติดหนึบ" มีประโยชน์เมื่อแสดงรายการข้อมูลที่จัดกลุ่ม ด้านล่างนี้คือตัวอย่าง "รายชื่อติดต่อ" ที่แบ่งกลุ่มตามอักษรนำของรายชื่อติดต่อแต่ละรายการ
หากต้องการใช้ส่วนหัวแบบติดหนึบด้วย 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) { // ... } }
สําหรับ Use Case ง่ายๆ แอปมักจําเป็นต้องทราบข้อมูลเกี่ยวกับรายการแรกที่มองเห็นเท่านั้น ด้วยเหตุนี้ 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() } } }
การอ่านสถานะในคอมโพสิชันโดยตรงจะมีประโยชน์เมื่อคุณต้องการอัปเดตคอมโพสิชัน UI อื่นๆ แต่ก็ยังมีบางกรณีที่ไม่จำเป็นต้องจัดการเหตุการณ์ในคอมโพสิชันเดียวกัน ตัวอย่างที่พบบ่อยของกรณีนี้คือการส่งเหตุการณ์ 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 ขึ้นไปรองรับการเขียนผ่านไลบรารี 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() } } } }
เคล็ดลับในการใช้เลย์เอาต์แบบ Lazy
ต่อไปนี้เป็นเคล็ดลับบางส่วนที่คุณควรคำนึงถึงเพื่อให้เลย์เอาต์แบบ Lazy ทำงานได้ตามที่ต้องการ
หลีกเลี่ยงการใช้รายการขนาด 0 พิกเซล
กรณีนี้อาจเกิดขึ้นเมื่อคุณคาดหวังว่าจะดึงข้อมูลบางอย่าง เช่น รูปภาพ มาใช้แบบไม่พร้อมกันเพื่อเติมรายการในรายการในขั้นตอนต่อมา ซึ่งจะทำให้เลย์เอาต์แบบ Lazy แสดงรายการทั้งหมดในการวัดผลครั้งแรก เนื่องจากรายการมีความสูง 0 พิกเซลและสามารถแสดงรายการทั้งหมดในวิวพอร์ตได้ เมื่อรายการโหลดและขยายความสูงแล้ว เลย์เอาต์แบบ Lazy จะทิ้งรายการอื่นๆ ทั้งหมดที่คอมโพสิทไว้โดยไม่จำเป็นในครั้งแรก เนื่องจากรายการเหล่านั้นไม่พอดีกับวิวพอร์ต หากต้องการหลีกเลี่ยงปัญหานี้ คุณควรกำหนดขนาดเริ่มต้นให้กับรายการต่างๆ เพื่อให้เลย์เอาต์แบบ 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
นั้นเพื่อส่งเนื้อหาประเภทต่างๆ ซึ่งช่วยให้คุณส่งออกรายการเดี่ยวและรายการหลายรายการในรายการได้ในที่เดียว
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 จะแสดงผล 2 รายการในบล็อกเดียว
LazyVerticalGrid( columns = GridCells.Adaptive(100.dp) ) { item { Item(0) } item { Item(1) Item(2) } item { Item(3) } // ... }
เลย์เอาต์แบบ Lazy จะจัดการเรื่องนี้ตามที่คาดไว้ โดยจะวางองค์ประกอบทีละรายการราวกับเป็นรายการที่แตกต่างกัน อย่างไรก็ตาม การดำเนินการดังกล่าวมีปัญหาอยู่ 2 อย่าง
เมื่อมีการแยกองค์ประกอบหลายรายการเป็นส่วนหนึ่งของรายการเดียว ระบบจะจัดการองค์ประกอบเหล่านั้นเป็นเอนทิตีเดียว ซึ่งหมายความว่าจะประกอบองค์ประกอบแต่ละรายการแยกกันไม่ได้อีกต่อไป หากองค์ประกอบหนึ่งปรากฏบนหน้าจอ องค์ประกอบทั้งหมดที่สอดคล้องกับรายการนั้นจะต้องได้รับการจัดองค์ประกอบและวัด ซึ่งอาจส่งผลเสียต่อประสิทธิภาพหากใช้มากเกินไป ในกรณีที่ใส่องค์ประกอบทั้งหมดไว้ในรายการเดียว จะทำให้การใช้เลย์เอาต์แบบ Lazy เสียเปล่า นอกจากปัญหาด้านประสิทธิภาพที่อาจเกิดขึ้นแล้ว การเพิ่มองค์ประกอบในรายการเดียวยังรบกวน scrollToItem()
และ animateScrollToItem()
ด้วย
อย่างไรก็ตาม ก็มี Use Case ที่ถูกต้องสำหรับการใช้องค์ประกอบหลายรายการในรายการเดียว เช่น การใช้ตัวแบ่งภายในรายการ คุณไม่ต้องการให้ตัวแบ่งเปลี่ยนตัวบ่งชี้การเลื่อน เนื่องจากไม่ควรถือว่าเป็นองค์ประกอบอิสระ นอกจากนี้ ประสิทธิภาพจะไม่ได้รับผลกระทบเนื่องจากตัวแบ่งมีขนาดเล็ก ตัวแบ่งอาจต้องแสดงเมื่อรายการก่อนหน้าแสดงอยู่ เพื่อให้เป็นส่วนหนึ่งของรายการก่อนหน้า
LazyVerticalGrid( columns = GridCells.Adaptive(100.dp) ) { item { Item(0) } item { Item(1) Divider() } item { Item(2) } // ... }
ลองใช้การจัดเรียงที่กำหนดเอง
โดยปกติแล้วรายการแบบ Lazy จะมีรายการจำนวนมากและกินพื้นที่มากกว่าขนาดของคอนเทนเนอร์การเลื่อน อย่างไรก็ตาม เมื่อรายการมีจำนวนไม่มากนัก การออกแบบอาจมีข้อกำหนดที่เฉพาะเจาะจงมากขึ้นเกี่ยวกับตำแหน่งที่ควรวางรายการเหล่านี้ในวิวพอร์ต
คุณสามารถใช้ประเภทธุรกิจที่กําหนดเอง Arrangement
และส่งไปยัง LazyColumn
ในตัวอย่างต่อไปนี้ TopWithFooter
ออบเจ็กต์จะต้องใช้เมธอด arrange
เท่านั้น ประการแรก ระบบจะจัดตำแหน่งรายการทีละรายการ ประการที่ 2 หากความสูงทั้งหมดที่ใช้ต่ำกว่าความสูงของวิวพอร์ต ระบบจะวางส่วนท้ายไว้ที่ด้านล่าง
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 เป็นต้นไป ให้พิจารณาเพิ่ม contentType
ลงในรายการหรือตารางกริดเพื่อให้เลย์เอาต์แบบ Lazy ทำงานได้อย่างมีประสิทธิภาพสูงสุด ซึ่งช่วยให้คุณระบุประเภทเนื้อหาสำหรับรายการแต่ละรายการของเลย์เอาต์ได้ในกรณีที่คุณกำลังเขียนรายการหรือตารางกริดที่มีรายการหลายประเภท
LazyColumn { items(elements, contentType = { it.type }) { // ... } }
เมื่อคุณระบุ contentType
แล้ว Compose จะใช้การคอมโพสซ้ำได้เฉพาะระหว่างรายการประเภทเดียวกันเท่านั้น เนื่องจากการนำมาใช้ซ้ำมีประสิทธิภาพมากขึ้นเมื่อคุณเขียนรายการที่มีโครงสร้างคล้ายกัน การระบุประเภทเนื้อหาจะช่วยให้มั่นใจได้ว่าเครื่องมือเขียนจะไม่พยายามเขียนรายการประเภท A ทับรายการประเภท B ที่แตกต่างออกไปโดยสิ้นเชิง วิธีนี้จะช่วยเพิ่มประโยชน์สูงสุดของการใช้องค์ประกอบซ้ำและประสิทธิภาพของเลย์เอาต์แบบ Lazy
การวัดประสิทธิภาพ
คุณจะวัดประสิทธิภาพของเลย์เอาต์แบบ Lazy ได้ก็ต่อเมื่อทํางานในโหมดรุ่นและเปิดใช้การเพิ่มประสิทธิภาพ R8 ในบิลด์แก้ไขข้อบกพร่อง การเลื่อนเลย์เอาต์แบบ Lazy อาจช้าลง อ่านข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้ได้ที่หัวข้อประสิทธิภาพการเขียน
แนะนำสำหรับคุณ
- หมายเหตุ: ข้อความลิงก์จะแสดงเมื่อ JavaScript ปิดอยู่
- ย้ายข้อมูล
RecyclerView
ไปยังรายการแบบ Lazy - บันทึกสถานะ UI ใน Compose
- Kotlin สำหรับ Jetpack Compose