การเลื่อนที่ซ้อนกันคือระบบที่คอมโพเนนต์การเลื่อนหลายรายการที่อยู่ภายในคอมโพเนนต์อื่นๆ ทำงานร่วมกันโดยตอบสนองต่อท่าทางการเลื่อนเดียวและสื่อสารเดลต้า (การเปลี่ยนแปลง) การเลื่อน
ระบบการเลื่อนที่ซ้อนกันช่วยให้สามารถประสานงานระหว่างคอมโพเนนต์ที่เลื่อนได้และลิงก์ตามลำดับชั้น (ส่วนใหญ่มักจะแชร์ระดับบนสุดเดียวกัน) ระบบนี้จะลิงก์คอนเทนเนอร์การเลื่อนและอนุญาตให้มีการโต้ตอบกับเดลต้าการเลื่อนที่เผยแพร่และแชร์ระหว่างกัน
Compose มีวิธีจัดการการเลื่อนที่ซ้อนกันระหว่าง Composable หลายวิธี ตัวอย่างทั่วไปของการเลื่อนที่ซ้อนกันคือรายการภายในรายการอื่น และกรณีที่ซับซ้อนกว่าคือแถบเครื่องมือที่ยุบได้
การเลื่อนที่ฝังอยู่โดยอัตโนมัติ
การเลื่อนที่ฝังแบบง่ายไม่จำเป็นต้องมีการดำเนินการใดๆ จากคุณ ท่าทางสัมผัสที่เริ่มการเลื่อนจะส่งต่อจากองค์ประกอบย่อยไปยังองค์ประกอบหลักโดยอัตโนมัติ เพื่อให้องค์ประกอบหลักจัดการท่าทางสัมผัสเมื่อองค์ประกอบย่อยเลื่อนต่อไปไม่ได้
คอมโพเนนต์และตัวแก้ไขบางรายการของ Compose รองรับและมีฟีเจอร์การเลื่อนที่ฝังอยู่โดยอัตโนมัติ ได้แก่
verticalScroll,
horizontalScroll,
scrollable,
API Lazy และ 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 จะช่วยให้คุณมีความยืดหยุ่นมากขึ้นด้วยการกำหนดลำดับชั้นการเลื่อนที่ฝังอยู่ ดังที่กล่าวไว้ในส่วนก่อนหน้า คอมโพเนนต์บางรายการมีการรองรับการเลื่อนที่ฝังในตัว อย่างไรก็ตาม สำหรับ Composable ที่ไม่สามารถเลื่อนได้โดยอัตโนมัติ เช่น Box หรือ Column เดลต้าการเลื่อนในคอมโพเนนต์ดังกล่าวจะไม่เผยแพร่ในระบบการเลื่อนที่ฝังอยู่ และเดลต้าจะไม่ไปถึง NestedScrollConnection หรือคอมโพเนนต์หลัก หากต้องการแก้ไขปัญหานี้ คุณสามารถใช้ nestedScroll เพื่อให้การสนับสนุนดังกล่าวแก่คอมโพเนนต์อื่นๆ รวมถึงคอมโพเนนต์ที่กำหนดเอง
รอบการเลื่อนที่ฝังไว้
วงจรการเลื่อนที่ซ้อนกันคือขั้นตอนของเดลต้าการเลื่อนที่ส่งขึ้นและลงในแผนผังลำดับชั้นผ่านคอมโพเนนต์ (หรือโหนด) ทั้งหมดที่เป็นส่วนหนึ่งของระบบการเลื่อนที่ซ้อนกัน เช่น โดยการใช้คอมโพเนนต์และตัวแก้ไขที่เลื่อนได้ หรือ nestedScroll
ระยะของวงจรการเลื่อนที่ฝังไว้
เมื่อคอมโพเนนต์ที่เลื่อนได้ตรวจพบเหตุการณ์ทริกเกอร์ (เช่น ท่าทางสัมผัส) ก่อนที่การดำเนินการเลื่อนจริงจะทริกเกอร์ ระบบจะส่งเดลต้าที่สร้างขึ้นไปยังระบบการเลื่อนที่ฝังอยู่และผ่าน 3 ระยะ ได้แก่ ก่อนเลื่อน การใช้โหนด และหลังเลื่อน
ในระยะแรกก่อนการเลื่อน คอมโพเนนต์ที่ได้รับเดลต้าของเหตุการณ์ทริกเกอร์จะส่งเหตุการณ์เหล่านั้นขึ้นไปตามแผนผังลำดับชั้นไปยังระดับบนสุด จากนั้นเหตุการณ์เดลต้าจะส่งต่อลงไป ซึ่งหมายความว่าเดลต้าจะส่งต่อจากระดับบนสุดลงไปยังระดับย่อยที่เริ่มวงจรการเลื่อนที่ฝังอยู่
ซึ่งจะช่วยให้องค์ประกอบหลักของการเลื่อนที่ฝังอยู่ (คอมโพสิเบิลที่ใช้ nestedScroll หรือตัวปรับแต่งที่เลื่อนได้) มีโอกาสดำเนินการบางอย่างกับเดลตาก่อนที่โหนดจะใช้เดลตานั้นได้
ในระยะการใช้โหนด โหนดจะใช้เดลต้าที่รายการหลักไม่ได้ใช้ ซึ่งเป็นช่วงที่การเลื่อนเกิดขึ้นจริงและมองเห็นได้
ในระยะนี้ บุตรหลานอาจเลือกใช้ม้วนคัมภีร์ที่เหลือทั้งหมดหรือบางส่วน ส่วนที่เหลือจะถูกส่งกลับขึ้นไปเพื่อผ่านขั้นตอนหลังการเลื่อน
สุดท้าย ในระยะหลังการเลื่อน สิ่งที่โหนดไม่ได้ใช้เองจะถูกส่งกลับไปยังระดับบนเพื่อใช้
ระยะหลังการเลื่อนจะทำงานในลักษณะเดียวกับระยะก่อนการเลื่อน ซึ่งผู้ปกครองอาจเลือกที่จะใช้หรือไม่ใช้ก็ได้
เช่นเดียวกับการเลื่อน เมื่อท่าทางการลากเสร็จสิ้น ระบบอาจแปลความตั้งใจของผู้ใช้เป็นความเร็วที่ใช้ในการเหวี่ยง (เลื่อนโดยใช้ภาพเคลื่อนไหว) คอนเทนเนอร์ที่เลื่อนได้ การเหวี่ยงยังเป็นส่วนหนึ่งของวงจรการเลื่อนที่ฝังอยู่ด้วย และความเร็วที่เกิดจากเหตุการณ์การลากจะผ่านระยะที่คล้ายกัน ได้แก่ ก่อนการเหวี่ยง การใช้โหนด และหลังการเหวี่ยง โปรดทราบว่าภาพเคลื่อนไหวแบบเหวี่ยงจะเชื่อมโยงกับท่าทางสัมผัสเท่านั้น และจะไม่ทริกเกอร์โดยเหตุการณ์อื่นๆ เช่น a11y หรือการเลื่อนด้วยฮาร์ดแวร์
เข้าร่วมในวงจรการเลื่อนที่ฝังอยู่
การเข้าร่วมในวงจรหมายถึงการสกัดกั้น การใช้ และการรายงานการใช้เดลต้าตามลำดับชั้น Compose มีชุดเครื่องมือที่ส่งผลต่อวิธีการทำงานของระบบการเลื่อนที่ฝังอยู่และวิธีโต้ตอบกับระบบโดยตรง เช่น เมื่อคุณต้องการดำเนินการบางอย่างกับเดลต้าการเลื่อนก่อนที่คอมโพเนนต์ที่เลื่อนได้จะเริ่มเลื่อน
หากวงจรการเลื่อนที่ฝังอยู่เป็นระบบที่ทำงานบนเชนของโหนด ตัวแก้ไข nestedScroll จะเป็นวิธีขัดจังหวะและแทรกเข้าไปในการเปลี่ยนแปลงเหล่านี้ และส่งผลต่อข้อมูล (เดลต้าการเลื่อน) ที่เผยแพร่ในเชน ตัวแก้ไขนี้สามารถวางไว้ที่ใดก็ได้ในลำดับชั้น และจะสื่อสารกับอินสแตนซ์ตัวแก้ไขการเลื่อนที่ฝังอยู่ด้านบนของต้นไม้เพื่อให้สามารถแชร์ข้อมูลผ่านช่องทางนี้ได้ องค์ประกอบของตัวแก้ไขนี้คือ NestedScrollConnection และ NestedScrollDispatcher
NestedScrollConnection เป็นวิธีตอบสนองต่อระยะต่างๆ ของวงจรการเลื่อนที่ฝังอยู่และส่งผลต่อระบบการเลื่อนที่ฝังอยู่ ซึ่งประกอบด้วยเมธอด Callback 4 เมธอด โดยแต่ละเมธอดจะแสดงระยะการใช้ 1 ระยะ ได้แก่ ก่อน/หลังการเลื่อน และก่อน/หลังการเหวี่ยง
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 } }
Callback แต่ละรายการยังให้ข้อมูลเกี่ยวกับเดลต้าที่เผยแพร่ด้วย ซึ่งได้แก่ เดลต้า 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 } } } }
Callback ทั้งหมดจะให้ข้อมูลเกี่ยวกับประเภท 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 ที่เลื่อนได้ใน Composable ที่เลื่อนได้ หรือในทางกลับกัน คุณอาจพบปัญหา ปัญหาที่เห็นได้ชัดที่สุดจะเกิดขึ้นเมื่อคุณเลื่อนองค์ประกอบย่อยและไปถึงขอบเขตเริ่มต้นหรือสิ้นสุด และคาดหวังให้องค์ประกอบหลักรับช่วงต่อการเลื่อน อย่างไรก็ตาม ลักษณะการทำงานที่คาดไว้นี้อาจไม่เกิดขึ้นหรืออาจไม่ทำงานตามที่คาดไว้
ปัญหานี้เป็นผลมาจากความคาดหวังที่สร้างขึ้นใน Composable ที่เลื่อนได้
คอมโพสิเบิลที่เลื่อนได้มีกฎ "เลื่อนที่ฝังอยู่โดยค่าเริ่มต้น" ซึ่งหมายความว่าคอนเทนเนอร์ที่เลื่อนได้จะต้องเข้าร่วมในเชนการเลื่อนที่ฝังอยู่ ทั้งในฐานะระดับบนสุดผ่าน NestedScrollConnection และในฐานะระดับย่อยผ่าน NestedScrollDispatcher
จากนั้นองค์ประกอบย่อยจะขับเคลื่อนการเลื่อนที่ฝังอยู่สำหรับองค์ประกอบหลักเมื่อองค์ประกอบย่อยอยู่ที่ขอบเขต ตัวอย่างเช่น กฎนี้ช่วยให้ Compose Pager และ Compose LazyRow ทำงานร่วมกันได้ดี อย่างไรก็ตาม เมื่อทำการเลื่อนเพื่อทำงานร่วมกันด้วย ViewPager2 หรือ RecyclerView เนื่องจากองค์ประกอบเหล่านี้ไม่ได้ใช้ NestedScrollingParent3 การเลื่อนอย่างต่อเนื่องจากองค์ประกอบย่อยไปยังองค์ประกอบหลักจึงเป็นไปไม่ได้
หากต้องการเปิดใช้ API การทำงานร่วมกันของการเลื่อนที่ฝังอยู่ระหว่างองค์ประกอบ View ที่เลื่อนได้และ Composable ที่เลื่อนได้ซึ่งฝังอยู่ทั้ง 2 ทิศทาง คุณสามารถใช้ API การทำงานร่วมกันของการเลื่อนที่ฝังอยู่เพื่อลดปัญหาเหล่านี้ได้ในสถานการณ์ต่อไปนี้
View หลักที่ทำงานร่วมกันซึ่งมี ComposeView ย่อย
คอมโพสิเบิลหลักที่ทำงานร่วมกัน View คือคอมโพสิเบิลที่ใช้ NestedScrollingParent3 อยู่แล้ว จึงสามารถรับเดลต้าการเลื่อนจากคอมโพสิเบิลย่อยที่ฝังอยู่ซึ่งทำงานร่วมกันได้ ComposeView จะทำหน้าที่เป็นองค์ประกอบย่อยในกรณีนี้ และจะต้องใช้ NestedScrollingChild3 (โดยอ้อม)
ตัวอย่างหนึ่งของแอปหลักที่ทำงานร่วมกันคือ androidx.coordinatorlayout.widget.CoordinatorLayout
หากต้องการความสามารถในการทำงานร่วมกันของการเลื่อนที่ฝังอยู่ระหว่างคอนเทนเนอร์หลัก View ที่เลื่อนได้และ Composable ย่อยที่เลื่อนได้ที่ฝังอยู่ คุณสามารถใช้ rememberNestedScrollInteropConnection() ได้
rememberNestedScrollInteropConnection()
อนุญาตและจดจำ
NestedScrollConnection
ที่เปิดใช้ความสามารถในการทำงานร่วมกันของการเลื่อนที่ฝังอยู่ระหว่าง View หลักที่ใช้
NestedScrollingParent3
และ Compose ย่อย โดยควรใช้ร่วมกับตัวแก้ไข nestedScroll เนื่องจากการเลื่อนที่ฝังอยู่เปิดใช้อยู่โดยค่าเริ่มต้นในฝั่ง Compose คุณจึงใช้การเชื่อมต่อนี้เพื่อเปิดใช้ทั้งการเลื่อนที่ฝังอยู่ฝั่ง View และเพิ่มตรรกะกาวที่จำเป็นระหว่าง Views กับ Composable ได้
กรณีการใช้งานที่พบบ่อยคือการใช้ CoordinatorLayout, CollapsingToolbarLayout และ Composable ย่อย ดังที่แสดงในตัวอย่างนี้
<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 คุณต้องตั้งค่า Composable ย่อยและ 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()) } } } } } } }
Composable หลักที่มี AndroidView ย่อย
สถานการณ์นี้ครอบคลุมการใช้งาน API การทำงานร่วมกันของการเลื่อนที่ซ้อนกันในฝั่ง Compose เมื่อคุณมี Composable หลักที่มี AndroidView ย่อย AndroidView จะใช้ NestedScrollDispatcher เนื่องจากทำหน้าที่เป็นองค์ประกอบย่อยขององค์ประกอบหลักที่เลื่อนได้ใน Compose รวมถึงใช้ NestedScrollingParent3 เนื่องจากทำหน้าที่เป็นองค์ประกอบหลักขององค์ประกอบย่อยที่เลื่อนได้ใน View จากนั้น Compose หลักจะสามารถรับเดลต้าการเลื่อนที่ฝังจาก Compose ย่อยที่เลื่อนได้ซึ่งฝังอยู่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( title = {}, 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(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 ซึ่งจะช่วยให้องค์ประกอบมีส่วนร่วมในการเลื่อนที่ซ้อนกัน แต่จะไม่เปิดใช้การเลื่อนองค์ประกอบโดยอัตโนมัติ สำหรับ Composable ที่ไม่สามารถเลื่อนได้โดยอัตโนมัติ เช่น Box หรือ Column เดลต้าการเลื่อนในคอมโพเนนต์ดังกล่าวจะไม่เผยแพร่ในระบบการเลื่อนที่ฝังอยู่ และเดลต้าจะไม่ไปถึง NestedScrollConnection ที่ rememberNestedScrollInteropConnection() มีให้ ดังนั้นเดลต้าเหล่านั้นจะไม่ไปถึงคอมโพเนนต์ View ระดับบนสุด หากต้องการแก้ไขปัญหานี้ ให้ตรวจสอบว่าคุณได้ตั้งค่าตัวแก้ไขที่เลื่อนได้ให้กับ Composable ที่ฝังประเภทเหล่านี้ด้วย คุณสามารถดูข้อมูลเพิ่มเติมได้ในส่วนก่อนหน้าเกี่ยวกับการเลื่อนที่ซ้อนกัน
องค์ประกอบหลักที่ไม่ทำงานร่วมกัน View ซึ่งมีองค์ประกอบย่อย ComposeView
มุมมองที่ไม่ทำงานร่วมกันคือมุมมองที่ไม่ได้ใช้อินเทอร์เฟซ NestedScrolling ที่จำเป็นในฝั่ง View โปรดทราบว่าการทำงานร่วมกันของการเลื่อนที่ฝังอยู่กับ Views เหล่านี้จะไม่ทำงานทันที Views ที่ไม่ให้ความร่วมมือคือ RecyclerView และ ViewPager2
แหล่งข้อมูลเพิ่มเติม
แนะนำสำหรับคุณ
- หมายเหตุ: ข้อความลิงก์จะแสดงเมื่อ JavaScript ปิดอยู่
- ทำความเข้าใจท่าทางสัมผัส
- ย้ายข้อมูล
CoordinatorLayoutไปยัง Compose - การใช้ View ใน Compose