แอปจำนวนมากจำเป็นต้องแสดงคอลเล็กชันรายการ เอกสารนี้จะอธิบายวิธีที่คุณ ก็ทำได้ใน 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 จะเพิ่มเนื้อหาของแต่ละรายการเป็น
ที่จำเป็นสำหรับเลย์เอาต์และตำแหน่งการเลื่อน
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 Loading
คอมโพสิเบิล 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
เป็น Composable ที่ช่วยให้คุณสร้างตารางกริดที่จัดเรียงแบบ 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() )
หากต้องการตั้งค่าจำนวนคอลัมน์คงที่ ให้ใช้ 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) } }
การระบุคีย์จะช่วยให้คุณ 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 นี้ใช้งานง่าย คุณเพียงแค่ต้องตั้งค่าตัวแก้ไข 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() } } }
การอ่านสถานะในการเขียนโดยตรงจะมีประโยชน์เมื่อคุณต้องการอัปเดต
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()
ของส่วนขยาย แล้วส่งผ่านฟังก์ชันส่วนขยาย
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
ต่อไปนี้เป็นเคล็ดลับบางส่วนที่คุณควรคำนึงถึงเพื่อให้เลย์เอาต์แบบ 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 { // ... } }
โดยจะได้ผลลัพธ์เดียวกันด้วยการรวม 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 รายการ หลังอีกชิ้นหนึ่ง ราวกับว่าเป็นคนละรายการกัน อย่างไรก็ตาม การดำเนินการดังกล่าวมีปัญหาอยู่ 2 อย่าง
เมื่อมีการแยกองค์ประกอบหลายรายการเป็นส่วนหนึ่งของรายการเดียว ระบบจะจัดการองค์ประกอบเหล่านั้นเป็นเอนทิตีเดียว ซึ่งหมายความว่าจะประกอบองค์ประกอบแต่ละรายการแยกกันไม่ได้อีกต่อไป หากมี
จะมองเห็นได้บนหน้าจอ จากนั้นองค์ประกอบทั้งหมดที่สอดคล้องกับ
รายการจะต้องได้รับการแต่งขึ้นและวัดค่า ซึ่งอาจส่งผลเสียต่อประสิทธิภาพหากใช้มากเกินไป ในกรณีที่ใส่องค์ประกอบทั้งหมดไว้ในรายการเดียว จะทำให้การใช้เลย์เอาต์แบบ Lazy เสียเปล่า นอกจากปัญหาด้านประสิทธิภาพที่อาจเกิดขึ้นแล้ว การเพิ่มองค์ประกอบในรายการเดียวยังรบกวน scrollToItem()
และ animateScrollToItem()
ด้วย
อย่างไรก็ตาม ก็มี Use Case ที่ถูกต้องสำหรับการใช้องค์ประกอบหลายรายการในรายการเดียว เช่น การใช้ตัวแบ่งภายในรายการ คุณไม่ต้องการให้เส้นแบ่งเปลี่ยนการเลื่อน เนื่องจากไม่ควรถือเป็นองค์ประกอบอิสระ นอกจากนี้ ประสิทธิภาพ จะไม่ได้รับผลกระทบเพราะเส้นแบ่งมีขนาดเล็ก ตัวแบ่งอาจต้อง แสดงเมื่อรายการก่อนที่จะเห็น เพื่อให้เป็นส่วนหนึ่งของ รายการ:
LazyVerticalGrid( columns = GridCells.Adaptive(100.dp) ) { item { Item(0) } item { Item(1) Divider() } item { Item(2) } // ... }
ลองใช้การจัดเรียงที่กำหนดเอง
โดยปกติแล้วรายการแบบ Lazy จะมีรายการจำนวนมากและกินพื้นที่มากกว่าขนาดของคอนเทนเนอร์การเลื่อน อย่างไรก็ตาม เมื่อในรายการมีการป้อนข้อมูล 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 เป็นต้นไป ให้พิจารณาเพิ่ม contentType
ลงในรายการหรือตารางกริดเพื่อให้เลย์เอาต์แบบ Lazy ทำงานได้อย่างมีประสิทธิภาพสูงสุด ซึ่งจะช่วยให้คุณระบุประเภทเนื้อหาสำหรับแต่ละเหตุการณ์ได้
ในกรณีที่คุณกำลังเขียนรายการหรือตารางกริดที่ประกอบด้วย
ของรายการประเภทต่างๆ หลายประเภท
LazyColumn { items(elements, contentType = { it.type }) { // ... } }
เมื่อคุณระบุ
contentType
,
การเขียนสามารถใช้การเรียบเรียงเพลงซ้ำเพียงอย่างเดียว
ระหว่างรายการที่อยู่ในประเภทเดียวกันได้ เพราะการนำมาใช้ซ้ำจะมีประสิทธิภาพมากกว่าเมื่อคุณ
เขียนรายการที่มีโครงสร้างคล้ายกัน ทำให้ประเภทเนื้อหา
การเขียนไม่ได้พยายามเขียนรายการประเภท A ซ้อนทับกับ
รายการอื่นของประเภท B ซึ่งจะช่วยเพิ่มประโยชน์สูงสุดของการใช้องค์ประกอบซ้ำและประสิทธิภาพของเลย์เอาต์แบบ Lazy
การวัดประสิทธิภาพ
คุณจะวัดประสิทธิภาพของเลย์เอาต์แบบ Lazy ได้ก็ต่อเมื่อทํางานในโหมดรุ่นและเปิดใช้การเพิ่มประสิทธิภาพ R8 เท่านั้น ในบิลด์แก้ไขข้อบกพร่อง การเลื่อนเลย์เอาต์แบบ Lazy อาจช้าลง อ่านข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้ได้ที่หัวข้อประสิทธิภาพการเขียน
แนะนำสำหรับคุณ
- หมายเหตุ: ข้อความลิงก์จะแสดงเมื่อ JavaScript ปิดอยู่
- ย้ายข้อมูล
RecyclerView
ไปยังรายการแบบ Lazy - บันทึกสถานะ UI ใน Compose
- Kotlin สำหรับ Jetpack Compose