การเลื่อนแบบซ้อนเป็นระบบที่คอมโพเนนต์การเลื่อนหลายรายการที่อยู่ภายในคอมโพเนนต์อื่นๆ ทำงานร่วมกันโดยตอบสนองต่อท่าทางสัมผัสการเลื่อนท่าทางเดียวและสื่อสารค่าเดลต้า (การเปลี่ยนแปลง) ของการเลื่อน
ระบบการเลื่อนแบบซ้อนช่วยให้คอมโพเนนต์ที่เลื่อนได้และเชื่อมโยงกันตามลำดับชั้น (ส่วนใหญ่จะแชร์คอมโพเนนต์ระดับบนสุดเดียวกัน) ทำงานร่วมกันได้ ระบบนี้จะลิงก์คอนเทนเนอร์การเลื่อนและอนุญาตให้มีการโต้ตอบกับค่าเดลต้าของการเลื่อนที่เผยแพร่และแชร์ระหว่างคอนเทนเนอร์
Compose มีวิธีจัดการการเลื่อนแบบซ้อนระหว่างคอมโพสได้หลายวิธี ตัวอย่างทั่วไปของการเลื่อนแบบซ้อนคือรายการที่อยู่ภายในอีกรายการหนึ่ง และกรณีที่ซับซ้อนกว่าคือแถบเครื่องมือแบบยุบได้
การเลื่อนแบบซ้อนอัตโนมัติ
การเลื่อนแบบซ้อนอย่างง่ายไม่จำเป็นต้องให้คุณดำเนินการใดๆ ระบบจะเผยแพร่ท่าทางสัมผัสที่เริ่มการดำเนินการเลื่อนจากคอมโพเนนต์ย่อยไปยังคอมโพเนนต์ระดับบนสุดโดยอัตโนมัติ เพื่อให้คอมโพเนนต์ระดับบนสุดจัดการท่าทางสัมผัสเมื่อคอมโพเนนต์ย่อยเลื่อนต่อไม่ได้
คอมโพเนนต์และตัวปรับแต่งบางรายการของ
Compose เช่น
verticalScroll,
horizontalScroll,
scrollable,
Lazy API และ TextField รองรับและมีการเลื่อนแบบซ้อนอัตโนมัติให้ใช้งานได้ทันที ซึ่งหมายความว่าเมื่อผู้ใช้เลื่อนคอมโพเนนต์ย่อยภายในของคอมโพเนนต์แบบซ้อน ตัวปรับแต่งก่อนหน้าจะเผยแพร่ค่าเดลต้าของการเลื่อนไปยังคอมโพเนนต์ระดับบนสุดที่รองรับการเลื่อนแบบซ้อน
ตัวอย่างต่อไปนี้แสดงองค์ประกอบที่มีตัวปรับแต่ง
verticalScroll
อยู่ภายในคอนเทนเนอร์ที่มีตัวปรับแต่งverticalScroll
ด้วย
@Composable private fun AutomaticNestedScroll() { val gradient = Brush.verticalGradient(0f to Color.Gray, 1000f to Color.White) Box( modifier = Modifier .background(Color.LightGray) .verticalScroll(rememberScrollState()) .padding(32.dp) ) { Column { repeat(6) { Box( modifier = Modifier .height(128.dp) .verticalScroll(rememberScrollState()) ) { Text( "Scroll here", modifier = Modifier .border(12.dp, Color.DarkGray) .background(brush = gradient) .padding(24.dp) .height(150.dp) ) } } } } }
การใช้ตัวปรับแต่ง nestedScroll
หากต้องการสร้างการเลื่อนที่ซับซ้อนและมีการประสานงานระหว่างองค์ประกอบหลายรายการ
ตัว
nestedScroll
ปรับแต่งจะช่วยให้คุณมีความยืดหยุ่นมากขึ้นโดยการกำหนดลำดับชั้นของการเลื่อนแบบซ้อน ดังที่กล่าวไว้ในส่วนก่อนหน้า คอมโพเนนต์บางรายการรองรับการเลื่อนแบบซ้อนในตัว อย่างไรก็ตาม สำหรับคอมโพสที่เลื่อนไม่ได้โดยอัตโนมัติ เช่น Box หรือ Column ค่าเดลต้าของการเลื่อนในคอมโพเนนต์ดังกล่าวจะไม่เผยแพร่ในระบบการเลื่อนแบบซ้อน และค่าเดลต้าจะไม่ไปถึง NestedScrollConnection หรือคอมโพเนนต์ระดับบนสุด คุณสามารถใช้ nestedScroll เพื่อให้การรองรับดังกล่าวแก่คอมโพเนนต์อื่นๆ รวมถึงคอมโพเนนต์ที่กำหนดเอง
วงจรการเลื่อนแบบซ้อน
วงจรการเลื่อนแบบซ้อนคือโฟลว์ของค่าเดลต้าของการเลื่อนที่ส่งขึ้นและลงในแผนผังลำดับชั้นผ่านคอมโพเนนต์ (หรือโหนด) ทั้งหมดที่เป็นส่วนหนึ่งของระบบการเลื่อนแบบซ้อน เช่น โดยใช้คอมโพเนนต์และตัวปรับแต่งที่เลื่อนได้ หรือ nestedScroll
ระยะของวงจรการเลื่อนแบบซ้อน
เมื่อคอมโพเนนต์ที่เลื่อนได้ตรวจพบเหตุการณ์ทริกเกอร์ (เช่น ท่าทางสัมผัส) ระบบจะส่งค่าเดลต้าที่สร้างขึ้นไปยังระบบการเลื่อนแบบซ้อนและผ่าน 3 ระยะ ได้แก่ ระยะก่อนเลื่อน ระยะการใช้โหนด และระยะหลังเลื่อน ก่อนที่จะทริกเกอร์การดำเนินการเลื่อนจริง
ในระยะก่อนเลื่อน ซึ่งเป็นระยะแรก คอมโพเนนต์ที่ได้รับค่าเดลต้าของเหตุการณ์ทริกเกอร์จะส่งเหตุการณ์เหล่านั้นขึ้นไปตามแผนผังลำดับชั้นไปยังคอมโพเนนต์ระดับบนสุด จากนั้นเหตุการณ์ค่าเดลต้าจะลอยลงมา ซึ่งหมายความว่าระบบจะเผยแพร่ค่าเดลต้าจากคอมโพเนนต์ระดับบนสุดไปยังคอมโพเนนต์ย่อยที่เริ่มวงจรการเลื่อนแบบซ้อน
ซึ่งจะทำให้คอมโพเนนต์ระดับบนสุดของการเลื่อนแบบซ้อน (คอมโพสที่ใช้ nestedScroll หรือตัวปรับแต่งที่เลื่อนได้) มีโอกาสดำเนินการบางอย่างกับค่าเดลต้าก่อนที่โหนดจะใช้ค่าเดลต้าได้
ในระยะการใช้โหนด โหนดจะใช้ค่าเดลต้าที่คอมโพเนนต์ระดับบนสุดไม่ได้ใช้ ซึ่งเป็นเวลาที่การเคลื่อนไหวของการเลื่อนเกิดขึ้นจริงและมองเห็นได้
ในระยะนี้ คอมโพเนนต์ย่อยอาจเลือกใช้การเลื่อนที่เหลือทั้งหมดหรือบางส่วน ระบบจะส่งการเลื่อนที่เหลือกลับขึ้นไปเพื่อผ่านระยะหลังเลื่อน
สุดท้าย ในระยะหลังเลื่อน ระบบจะส่งการเลื่อนที่โหนดไม่ได้ใช้กลับขึ้นไปอีกครั้งเพื่อให้คอมโพเนนต์ระดับบนสุดใช้
ระยะหลังเลื่อนทำงานในลักษณะเดียวกับระยะก่อนเลื่อน โดยคอมโพเนนต์ระดับบนสุดอาจเลือกใช้หรือไม่ใช้ก็ได้
เช่นเดียวกับการเลื่อน เมื่อท่าทางสัมผัสลากเสร็จสิ้น ระบบอาจแปลความตั้งใจของผู้ใช้เป็นอัตราความเร็วที่ใช้ในการตวัด (เลื่อนโดยใช้ภาพเคลื่อนไหว) คอนเทนเนอร์ที่เลื่อนได้ การปัดยังเป็นส่วนหนึ่งของวงจรการเลื่อนแบบซ้อน และความเร็วที่สร้างขึ้นโดยเหตุการณ์การลากจะผ่านระยะที่คล้ายกัน ได้แก่ ระยะก่อนปัด ระยะการใช้โหนด และระยะหลังปัด โปรดทราบว่าภาพเคลื่อนไหวการปัดจะเชื่อมโยงกับท่าทางสัมผัสเท่านั้น และจะไม่ทริกเกอร์โดยเหตุการณ์อื่นๆ เช่น การเลื่อน a11y หรือการเลื่อนฮาร์ดแวร์
มีส่วนร่วมในวงจรการเลื่อนแบบซ้อน
การมีส่วนร่วมในวงจรหมายถึงการสกัดกั้น การใช้ และการรายงานการใช้ค่าเดลต้าตามลำดับชั้น Compose มีชุดเครื่องมือเพื่อควบคุมวิธีที่ระบบการเลื่อนแบบซ้อนทำงานและวิธีโต้ตอบกับระบบโดยตรง เช่น เมื่อคุณต้องการดำเนินการบางอย่างกับค่าเดลต้าของการเลื่อนก่อนที่คอมโพเนนต์ที่เลื่อนได้จะเริ่มเลื่อน
หากวงจรการเลื่อนแบบซ้อนเป็นระบบที่ทำงานกับสายของโหนด ตัวปรับแต่ง
nestedScroll
จะเป็นวิธีสกัดกั้นและแทรกการเปลี่ยนแปลงเหล่านี้ รวมถึง
ควบคุมข้อมูล (ค่าเดลต้าของการเลื่อน) ที่เผยแพร่ในสาย คุณสามารถวางตัวปรับแต่งนี้ไว้ที่ใดก็ได้ในลำดับชั้น และตัวปรับแต่งจะสื่อสารกับอินสแตนซ์ตัวปรับแต่งการเลื่อนแบบซ้อนขึ้นไปตามแผนผังเพื่อให้แชร์ข้อมูลผ่านช่องทางนี้ได้ บล็อกการสร้างของตัวปรับแต่งนี้คือ NestedScrollConnection และ NestedScrollDispatcher
NestedScrollConnection
มีวิธีตอบสนองต่อระยะของวงจรการเลื่อนแบบซ้อนและควบคุมระบบการเลื่อนแบบซ้อน ประกอบด้วยเมธอดเรียกกลับ 4 รายการ ซึ่งแต่ละรายการแสดงถึงระยะการใช้ระยะหนึ่ง ได้แก่ ก่อน/หลังเลื่อน และก่อน/หลังปัด
val nestedScrollConnection = object : NestedScrollConnection { override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { println("Received onPreScroll callback.") return Offset.Zero } override fun onPostScroll( consumed: Offset, available: Offset, source: NestedScrollSource ): Offset { println("Received onPostScroll callback.") return Offset.Zero } }
การเรียกกลับแต่ละรายการยังให้ข้อมูลเกี่ยวกับค่าเดลต้าที่เผยแพร่ ได้แก่ ค่าเดลต้า available สำหรับระยะนั้นๆ และค่าเดลต้า consumed ที่ใช้ในระยะก่อนหน้า หากต้องการหยุดเผยแพร่ค่าเดลต้าขึ้นไปตามลำดับชั้นเมื่อใดก็ได้ คุณสามารถใช้การเชื่อมต่อการเลื่อนแบบซ้อนเพื่อดำเนินการดังกล่าวได้
val disabledNestedScrollConnection = remember { object : NestedScrollConnection { override fun onPostScroll( consumed: Offset, available: Offset, source: NestedScrollSource ): Offset { return if (source == NestedScrollSource.SideEffect) { available } else { Offset.Zero } } } }
การเรียกกลับทั้งหมดให้ข้อมูลเกี่ยวกับประเภท
NestedScrollSource
NestedScrollDispatcher
เริ่มต้นวงจรการเลื่อนแบบซ้อน การใช้ตัวส่งและเรียกเมธอดของตัวส่งจะทริกเกอร์วงจร คอนเทนเนอร์ที่เลื่อนได้มีตัวส่งในตัวที่ส่งค่าเดลต้าที่บันทึกไว้ระหว่างท่าทางสัมผัสไปยังระบบ ด้วยเหตุนี้ กรณีการใช้งานส่วนใหญ่ของการปรับแต่งการเลื่อนแบบซ้อนจึงเกี่ยวข้องกับการใช้ NestedScrollConnection แทนตัวส่ง เพื่อตอบสนองต่อค่าเดลต้าที่มีอยู่แล้วแทนที่จะส่งค่าเดลต้าใหม่
ดูการใช้งานเพิ่มเติมได้ที่
NestedScrollDispatcherSample
ปรับขนาดรูปภาพเมื่อเลื่อน
เมื่อผู้ใช้เลื่อน คุณสามารถสร้างเอฟเฟกต์ภาพแบบไดนามิกที่รูปภาพเปลี่ยนขนาดตามตำแหน่งการเลื่อน
ปรับขนาดรูปภาพตามตำแหน่งการเลื่อน
ข้อมูลโค้ดนี้แสดงการปรับขนาดรูปภาพภายใน LazyColumn ตามตำแหน่งการเลื่อนแนวตั้ง รูปภาพจะเล็กลงเมื่อผู้ใช้เลื่อนลง และใหญ่ขึ้นเมื่อเลื่อนขึ้น โดยยังคงอยู่ในขอบเขตขนาดต่ำสุดและสูงสุดที่กำหนด
@Composable fun ImageResizeOnScrollExample( modifier: Modifier = Modifier, maxImageSize: Dp = 300.dp, minImageSize: Dp = 100.dp ) { var currentImageSize by remember { mutableStateOf(maxImageSize) } var imageScale by remember { mutableFloatStateOf(1f) } val nestedScrollConnection = remember { object : NestedScrollConnection { override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { // Calculate the change in image size based on scroll delta val delta = available.y val newImageSize = currentImageSize + delta.dp val previousImageSize = currentImageSize // Constrain the image size within the allowed bounds currentImageSize = newImageSize.coerceIn(minImageSize, maxImageSize) val consumed = currentImageSize - previousImageSize // Calculate the scale for the image imageScale = currentImageSize / maxImageSize // Return the consumed scroll amount return Offset(0f, consumed.value) } } } Box(Modifier.nestedScroll(nestedScrollConnection)) { LazyColumn( Modifier .fillMaxWidth() .padding(15.dp) .offset { IntOffset(0, currentImageSize.roundToPx()) } ) { // Placeholder list items items(100, key = { it }) { Text( text = "Item: $it", style = MaterialTheme.typography.bodyLarge ) } } Image( painter = ColorPainter(Color.Red), contentDescription = "Red color image", Modifier .size(maxImageSize) .align(Alignment.TopCenter) .graphicsLayer { scaleX = imageScale scaleY = imageScale // Center the image vertically as it scales translationY = -(maxImageSize.toPx() - currentImageSize.toPx()) / 2f } ) } }
ประเด็นสำคัญเกี่ยวกับโค้ด
- โค้ดนี้ใช้
NestedScrollConnectionเพื่อสกัดกั้นเหตุการณ์การเลื่อน onPreScrollคำนวณการเปลี่ยนแปลงขนาดรูปภาพตามค่าเดลต้าของการเลื่อน- ตัวแปรสถานะ
currentImageSizeจะเก็บขนาดปัจจุบันของรูปภาพ โดยจำกัดไว้ระหว่างminImageSizeและmaxImageSize. imageScaleได้มาจากcurrentImageSize LazyColumnจะชดเชยตามcurrentImageSizeImageใช้ตัวปรับแต่งgraphicsLayerเพื่อใช้สเกลที่คำนวณtranslationYภายในgraphicsLayerช่วยให้รูปภาพยังคงอยู่ตรงกลางในแนวตั้งขณะที่ปรับสเกล
ผลลัพธ์
ข้อมูลโค้ดก่อนหน้าจะทำให้เกิดเอฟเฟกต์การปรับสเกลรูปภาพเมื่อเลื่อน
การทำงานร่วมกันของการเลื่อนแบบซ้อน
เมื่อพยายามซ้อนองค์ประกอบ View ที่เลื่อนได้ในคอมโพสที่เลื่อนได้ หรือในทางกลับกัน คุณอาจพบปัญหา ปัญหาที่เห็นได้ชัดเจนที่สุดจะเกิดขึ้นเมื่อคุณเลื่อนคอมโพเนนต์ย่อยและไปถึงขอบเขตเริ่มต้นหรือสิ้นสุด และคาดหวังให้คอมโพเนนต์ระดับบนสุดรับการเลื่อนต่อ อย่างไรก็ตาม ลักษณะการทำงานที่คาดหวังนี้อาจไม่เกิดขึ้นหรืออาจไม่ทำงานตามที่คาดไว้
ปัญหานี้เกิดจากความคาดหวังที่สร้างขึ้นในคอมโพสที่เลื่อนได้
คอมโพสที่เลื่อนได้มีกฎ "การเลื่อนแบบซ้อนโดยค่าเริ่มต้น" ซึ่งหมายความว่า
คอนเทนเนอร์ที่เลื่อนได้ทั้งหมดต้องมีส่วนร่วมในสายการเลื่อนแบบซ้อน ทั้งในฐานะ
คอมโพเนนต์ระดับบนสุดผ่าน
NestedScrollConnection,
และในฐานะคอมโพเนนต์ย่อยผ่าน
NestedScrollDispatcher.
จากนั้นคอมโพเนนต์ย่อยจะขับเคลื่อนการเลื่อนแบบซ้อนสำหรับคอมโพเนนต์ระดับบนสุดเมื่อคอมโพเนนต์ย่อยอยู่ที่ขอบเขต ตัวอย่างเช่น กฎนี้ช่วยให้ Pager และ LazyRow ของ Compose ทำงานร่วมกันได้ดี อย่างไรก็ตาม เมื่อมีการเลื่อนเพื่อการทำงานร่วมกัน
ด้วย ViewPager2 หรือ RecyclerView การเลื่อนอย่างต่อเนื่องจากคอมโพเนนต์ย่อยไปยังคอมโพเนนต์ระดับบนสุดจะไม่สามารถทำได้เนื่องจากคอมโพเนนต์เหล่านี้ไม่ได้ใช้
NestedScrollingParent3
หากต้องการเปิดใช้ API การทำงานร่วมกันของการเลื่อนแบบซ้อนระหว่างองค์ประกอบ View ที่เลื่อนได้และคอมโพสที่เลื่อนได้ ซึ่งซ้อนกันทั้ง 2 ทิศทาง คุณสามารถใช้ API การทำงานร่วมกันของการเลื่อนแบบซ้อนเพื่อลดปัญหาเหล่านี้ในสถานการณ์ต่อไปนี้
View ระดับบนสุดที่ทำงานร่วมกันซึ่งมี ComposeView ระดับล่าง
ระดับบนสุดที่ทำงานร่วมกัน View คือ ที่ใช้
NestedScrollingParent3
อยู่แล้ว จึงรับค่าเดลต้าของการเลื่อนจากระดับล่างแบบซ้อนที่ทำงานร่วมกันได้
ComposeView จะทำหน้าที่เป็นคอมโพเนนต์ย่อยในกรณีนี้และจะต้อง
ใช้
NestedScrollingChild3 (โดยอ้อม)
ตัวอย่างหนึ่งของคอมโพเนนต์ระดับบนสุดที่ทำงานร่วมกันคือ androidx.coordinatorlayout.widget.CoordinatorLayout
หากต้องการความสามารถในการทำงานร่วมกันของการเลื่อนแบบซ้อนระหว่างคอนเทนเนอร์ระดับบนสุดที่เลื่อนได้และคอมโพสระดับล่างที่เลื่อนได้แบบซ้อน คุณสามารถใช้
rememberNestedScrollInteropConnection()View
rememberNestedScrollInteropConnection() อนุญาตและจดจำNestedScrollConnection ที่เปิดใช้ความสามารถในการทำงานร่วมกันของการเลื่อนแบบซ้อนระหว่างคอมโพเนนต์ระดับบนสุด View ที่ใช้NestedScrollingParent3 และคอมโพเนนต์ระดับล่างของ Compose ควรใช้ร่วมกับตัวปรับแต่ง
nestedScroll
เนื่องจากระบบเปิดใช้การเลื่อนแบบซ้อนในฝั่ง Compose โดยค่าเริ่มต้น คุณ
จึงใช้การเชื่อมต่อนี้เพื่อเปิดใช้การเลื่อนแบบซ้อนในฝั่ง View และเพิ่ม
ตรรกะการเชื่อมต่อที่จำเป็นระหว่าง Views และคอมโพสได้
กรณีการใช้งานที่พบบ่อยคือการใช้ CoordinatorLayout, CollapsingToolbarLayout และคอมโพสระดับล่าง ดังที่แสดงในตัวอย่างนี้
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <com.google.android.material.appbar.AppBarLayout android:id="@+id/app_bar" android:layout_width="match_parent" android:layout_height="100dp" android:fitsSystemWindows="true"> <com.google.android.material.appbar.CollapsingToolbarLayout android:id="@+id/collapsing_toolbar_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" app:layout_scrollFlags="scroll|exitUntilCollapsed"> <!--...--> </com.google.android.material.appbar.CollapsingToolbarLayout> </com.google.android.material.appbar.AppBarLayout> <androidx.compose.ui.platform.ComposeView android:id="@+id/compose_view" app:layout_behavior="@string/appbar_scrolling_view_behavior" android:layout_width="match_parent" android:layout_height="match_parent"/> </androidx.coordinatorlayout.widget.CoordinatorLayout>
ในกิจกรรมหรือ Fragment คุณต้องตั้งค่าคอมโพสระดับล่างและ
ที่จำเป็น
NestedScrollConnection
open class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) findViewById<ComposeView>(R.id.compose_view).apply { setContent { val nestedScrollInterop = rememberNestedScrollInteropConnection() // Add the nested scroll connection to your top level @Composable element // using the nestedScroll modifier. LazyColumn(modifier = Modifier.nestedScroll(nestedScrollInterop)) { items(20) { item -> Box( modifier = Modifier .padding(16.dp) .height(56.dp) .fillMaxWidth() .background(Color.Gray), contentAlignment = Alignment.Center ) { Text(item.toString()) } } } } } } }
คอมโพสระดับบนสุดที่มี AndroidView ระดับล่าง
สถานการณ์นี้ครอบคลุมการใช้ API การทำงานร่วมกันของการเลื่อนแบบซ้อนในฝั่ง Compose เมื่อคุณมีคอมโพสระดับบนสุดที่มี AndroidView ระดับล่าง The AndroidView ใช้
NestedScrollDispatcher
เนื่องจากทำหน้าที่เป็นคอมโพเนนต์ย่อยของคอมโพเนนต์ระดับบนสุดที่เลื่อนได้ของ Compose รวมถึง
NestedScrollingParent3
เนื่องจากทำหน้าที่เป็นคอมโพเนนต์ระดับบนสุดของคอมโพเนนต์ย่อย View ที่เลื่อนได้ จากนั้นคอมโพสระดับบนสุดจะรับค่าเดลต้าของการเลื่อนแบบซ้อนจากคอมโพเนนต์ระดับล่าง View ที่เลื่อนได้แบบซ้อนได้
ตัวอย่างต่อไปนี้แสดงวิธีที่คุณสามารถใช้การทำงานร่วมกันของการเลื่อนแบบซ้อนในสถานการณ์นี้ รวมถึงแถบเครื่องมือแบบยุบได้ของ Compose
@Composable
private fun NestedScrollInteropComposeParentWithAndroidChildExample() {
val toolbarHeightPx = with(LocalDensity.current) { ToolbarHeight.roundToPx().toFloat() }
val toolbarOffsetHeightPx = remember { mutableStateOf(0f) }
// Sets up the nested scroll connection between the Box composable parent
// and the child AndroidView containing the RecyclerView
val nestedScrollConnection = remember {
object : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
// Updates the toolbar offset based on the scroll to enable
// collapsible behaviour
val delta = available.y
val newOffset = toolbarOffsetHeightPx.value + delta
toolbarOffsetHeightPx.value = newOffset.coerceIn(-toolbarHeightPx, 0f)
return Offset.Zero
}
}
}
Box(
Modifier
.fillMaxSize()
.nestedScroll(nestedScrollConnection)
) {
TopAppBar(
modifier = Modifier
.height(ToolbarHeight)
.offset { IntOffset(x = 0, y = toolbarOffsetHeightPx.value.roundToInt()) }
)
AndroidView(
{ context ->
LayoutInflater.from(context)
.inflate(R.layout.view_in_compose_nested_scroll_interop, null).apply {
with(findViewById<RecyclerView>(R.id.main_list)) {
layoutManager = LinearLayoutManager(context, VERTICAL, false)
adapter = NestedScrollInteropAdapter()
}
}.also {
// Nested scrolling interop is enabled when
// nested scroll is enabled for the root View
ViewCompat.setNestedScrollingEnabled(it, true)
}
},
// ...
)
}
}
private class NestedScrollInteropAdapter :
Adapter<NestedScrollInteropAdapter.NestedScrollInteropViewHolder>() {
val items = (1..10).map { it.toString() }
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): NestedScrollInteropViewHolder {
return NestedScrollInteropViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.list_item, parent, false)
)
}
override fun onBindViewHolder(holder: NestedScrollInteropViewHolder, position: Int) {
// ...
}
class NestedScrollInteropViewHolder(view: View) : ViewHolder(view) {
fun bind(item: String) {
// ...
}
}
// ...
}
ตัวอย่างนี้แสดงวิธีใช้ API กับตัวปรับแต่ง scrollable
@Composable
fun ViewInComposeNestedScrollInteropExample() {
Box(
Modifier
.fillMaxSize()
.scrollable(rememberScrollableState {
// View component deltas should be reflected in Compose
// components that participate in nested scrolling
it
}, Orientation.Vertical)
) {
AndroidView(
{ context ->
LayoutInflater.from(context)
.inflate(android.R.layout.list_item, null)
.apply {
// Nested scrolling interop is enabled when
// nested scroll is enabled for the root View
ViewCompat.setNestedScrollingEnabled(this, true)
}
}
)
}
}
และสุดท้าย ตัวอย่างนี้แสดงวิธีใช้ API การทำงานร่วมกันของการเลื่อนแบบซ้อนกับ
BottomSheetDialogFragment
เพื่อให้ได้ลักษณะการทำงานของการลากและปิดที่สำเร็จ
class BottomSheetFragment : BottomSheetDialogFragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val rootView: View = inflater.inflate(R.layout.fragment_bottom_sheet, container, false)
rootView.findViewById<ComposeView>(R.id.compose_view).apply {
setContent {
val nestedScrollInterop = rememberNestedScrollInteropConnection()
LazyColumn(
Modifier
.nestedScroll(nestedScrollInterop)
.fillMaxSize()
) {
item {
Text(text = "Bottom sheet title")
}
items(10) {
Text(
text = "List item number $it",
modifier = Modifier.fillMaxWidth()
)
}
}
}
return rootView
}
}
}
โปรดทราบว่า
rememberNestedScrollInteropConnection()
จะติดตั้ง
NestedScrollConnection
ในองค์ประกอบที่คุณแนบ NestedScrollConnection มีหน้าที่ส่งค่าเดลต้าจากระดับ Compose ไปยังระดับ View ซึ่งจะช่วยให้องค์ประกอบมีส่วนร่วมในการเลื่อนแบบซ้อน แต่ไม่ได้เปิดใช้การเลื่อนองค์ประกอบโดยอัตโนมัติ สำหรับคอมโพสที่เลื่อนไม่ได้โดยอัตโนมัติ เช่น Box หรือ Column ค่าเดลต้าของการเลื่อนในคอมโพเนนต์ดังกล่าวจะไม่เผยแพร่ในระบบการเลื่อนแบบซ้อน และค่าเดลต้าจะไม่ไปถึง NestedScrollConnection ที่ rememberNestedScrollInteropConnection() ให้ไว้ ดังนั้นค่าเดลต้าเหล่านั้นจะไม่ไปถึงคอมโพเนนต์ระดับบนสุด View หากต้องการแก้ไขปัญหานี้ ให้ตรวจสอบว่าคุณได้ตั้งค่าตัวปรับแต่งที่เลื่อนได้ให้กับคอมโพสแบบซ้อนประเภทนี้ด้วย ดูข้อมูลโดยละเอียดเพิ่มเติมได้ในส่วนก่อนหน้าเกี่ยวกับการเลื่อนแบบซ้อน
ระดับบนสุดที่ไม่ทำงานร่วมกันซึ่งมี ระดับล่างViewComposeView
View ที่ไม่ทำงานร่วมกันคือ View ที่ไม่ได้ใช้อินเทอร์เฟซ NestedScrolling ที่จำเป็นในฝั่ง View โปรดทราบว่าการทำงานร่วมกันของการเลื่อนแบบซ้อนกับ Views เหล่านี้จะไม่ทำงานทันที Views ที่ไม่ทำงานร่วมกันคือ RecyclerView และ ViewPager2
แหล่งข้อมูลเพิ่มเติม
แนะนำสำหรับคุณ
- หมายเหตุ: ข้อความลิงก์จะแสดงเมื่อ JavaScript ปิดอยู่
- ทำความเข้าใจท่าทาง
- ย้ายข้อมูล
CoordinatorLayoutไปยัง Compose - การใช้ View ใน Compose