Đối tượng sửa đổi cho thao tác cuộn lồng

Cuộn lồng là một hệ thống trong đó nhiều thành phần cuộn chứa trong nhau hoạt động cùng nhau bằng cách phản ứng với một thao tác cuộn duy nhất và truyền đạt các delta cuộn (thay đổi).

Hệ thống cuộn lồng cho phép phối hợp giữa các thành phần có thể cuộn và được liên kết theo thứ bậc (thường là bằng cách dùng chung thành phần mẹ). Hệ thống này liên kết các vùng chứa cuộn và cho phép tương tác với các delta cuộn đang được truyền và chia sẻ giữa các vùng chứa đó.

Compose cung cấp nhiều cách xử lý thao tác cuộn lồng giữa các thành phần kết hợp. Ví dụ điển hình của tính năng cuộn lồng là một danh sách con nằm trong một danh sách lớn hơn. Ví dụ phức tạp hơn là thanh công cụ có thể thu gọn.

Tự động lồng tính năng cuộn trong lớp khác

Bạn không cần làm gì khi chỉ đơn giản lồng tính năng cuộn trong lớp khác. Những cử chỉ khởi tạo thao tác cuộn sẽ tự động được truyền tải từ phần tử con đến phần tử mẹ, chẳng hạn như khi phần tử con không thể cuộn thêm nữa thì cử chỉ sẽ do phần tử mẹ xử lý.

Một số thành phần và đối tượng sửa đổi của Compose hỗ trợ và cung cấp tính năng cuộn dạng lồng tự động ngay từ đầu như: các API verticalScroll, horizontalScroll, scrollable, Lazy và TextField. Tức là khi người dùng cuộn một phần tử con bên trong các thành phần lồng nhau, thì các đối tượng sửa đổi trước đó sẽ truyền delta cuộn đến những phần tử mẹ có hỗ trợ tính năng cuộn lồng.

Ví dụ sau đây cho thấy các phần tử có đối tượng sửa đổi verticalScroll được áp dụng cho chúng bên trong một vùng chứa cũng có đối tượng sửa đổi verticalScroll được áp dụng cho nó.

@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)
                    )
                }
            }
        }
    }
}

Hai thành phần có thể cuộn dọc trên giao diện người dùng được lồng vào nhau, phản hồi với các cử chỉ bên trong và bên ngoài phần tử
Hình 1. Hai phần tử trên giao diện người dùng có thể cuộn dọc được lồng vào nhau, phản hồi với các cử chỉ bên trong và bên ngoài phần tử bên trong.

Sử dụng đối tượng sửa đổi nestedScroll

Nếu bạn cần tạo một thao tác cuộn phối hợp nâng cao giữa nhiều phần tử, đối tượng nestedScroll sửa đổi sẽ giúp bạn tăng tính linh hoạt bằng cách xác định một hệ thống phân cấp cuộn khi lồng ghép vào nhau. Như đã đề cập trong phần trước, một số thành phần đã tích hợp sẵn chức năng hỗ trợ cuộn lồng. Tuy nhiên, đối với các thành phần kết hợp không tự động cuộn được, chẳng hạn như Box hoặc Column, các delta cuộn trên những thành phần đó sẽ không truyền trong hệ thống cuộn lồng, đồng thời sẽ không tiếp cận được NestedScrollConnection cũng như thành phần mẹ. Để giải quyết vấn đề này, bạn có thể sử dụng nestedScroll để trao quyền hỗ trợ cho các thành phần khác, bao gồm cả thành phần tuỳ chỉnh.

Chu kỳ cuộn lồng

Chu kỳ cuộn lồng là luồng delta cuộn được điều phối lên và xuống cây phân cấp thông qua tất cả các thành phần (hoặc nút) là một phần của hệ thống cuộn lồng, chẳng hạn như bằng cách sử dụng các thành phần và đối tượng sửa đổi có thể cuộn hoặc nestedScroll.

Các giai đoạn của chu kỳ cuộn lồng

Khi một sự kiện kích hoạt (ví dụ: một cử chỉ) được thành phần có thể cuộn phát hiện, trước khi thao tác cuộn thực tế được kích hoạt, các delta được tạo sẽ được gửi đến hệ thống cuộn lồng và trải qua 3 giai đoạn: trước khi cuộn, tiêu thụ nút và sau khi cuộn.

Các giai đoạn của chu kỳ cuộn lồng nhau
Hình 2. Các giai đoạn của chu kỳ cuộn lồng.

Trong giai đoạn đầu tiên (trước khi cuộn), thành phần nhận được các delta sự kiện kích hoạt sẽ điều phối các sự kiện đó lên, thông qua cây phân cấp, đến thành phần mẹ trên cùng. Sau đó, các sự kiện delta sẽ lan xuống, nghĩa là các delta sẽ được truyền từ thành phần mẹ gốc xuống thành phần con đã bắt đầu chu kỳ cuộn lồng.

Giai đoạn trước khi cuộn – gửi sự kiện lên
Hình 3. Giai đoạn trước khi cuộn: điều phối lên.

Điều này cho phép các thành phần mẹ cuộn lồng (thành phần kết hợp sử dụng nestedScroll hoặc đối tượng sửa đổi có thể cuộn) có cơ hội thực hiện điều gì đó với delta trước khi chính nút đó có thể tiêu thụ delta.

Giai đoạn trước khi cuộn – nổi bọt xuống
Hình 4. Giai đoạn trước khi cuộn – lan xuống.

Trong giai đoạn tiêu thụ nút, chính nút đó sẽ sử dụng bất kỳ delta nào mà thành phần mẹ của nút đó chưa sử dụng. Đây là thời điểm thao tác cuộn thực sự được thực hiện và hiển thị.

Giai đoạn sử dụng nút
Hình 5. Giai đoạn tiêu thụ nút.

Trong giai đoạn này, thành phần con có thể chọn tiêu thụ toàn bộ hoặc một phần thao tác cuộn còn lại. Mọi thứ còn lại sẽ được gửi trở lại để trải qua giai đoạn sau khi cuộn.

Cuối cùng, trong giai đoạn sau khi cuộn, mọi thứ mà chính nút đó không tiêu thụ sẽ được gửi lại cho các thành phần tổ tiên để tiêu thụ.

Giai đoạn sau khi cuộn – gửi lên
Hình 6. Giai đoạn sau khi cuộn – điều phối lên.

Giai đoạn sau khi cuộn hoạt động tương tự như giai đoạn trước khi cuộn, trong đó bất kỳ thành phần mẹ nào cũng có thể chọn tiêu thụ hoặc không.

Giai đoạn sau khi cuộn – nổi bọt
xuống
Hình 7. Giai đoạn sau khi cuộn – lan xuống.

Tương tự như thao tác cuộn, khi một cử chỉ kéo kết thúc, ý định của người dùng có thể được chuyển thành vận tốc dùng để vuốt (cuộn bằng ảnh động) vùng chứa có thể cuộn. Thao tác vuốt cũng là một phần của chu kỳ cuộn lồng và các vận tốc do sự kiện kéo tạo ra sẽ trải qua các giai đoạn tương tự: trước khi vuốt, tiêu thụ nút và sau khi vuốt. Xin lưu ý rằng ảnh động vuốt chỉ liên kết với cử chỉ chạm và sẽ không được kích hoạt bởi các sự kiện khác, chẳng hạn như cuộn phần cứng hoặc a11y.

Tham gia vào chu kỳ cuộn lồng

Việc tham gia vào chu kỳ này có nghĩa là chặn, tiêu thụ và báo cáo việc tiêu thụ các delta dọc theo hệ phân cấp. Compose cung cấp một bộ công cụ để ảnh hưởng đến cách hệ thống cuộn lồng hoạt động và cách tương tác trực tiếp với hệ thống đó, chẳng hạn như khi bạn cần thực hiện điều gì đó với các delta cuộn trước khi thành phần có thể cuộn bắt đầu cuộn.

Nếu chu kỳ cuộn lồng là một hệ thống hoạt động trên một chuỗi nút, thì đối tượng sửa đổi nestedScroll là một cách chặn và chèn vào những thay đổi này, đồng thời ảnh hưởng đến dữ liệu (delta cuộn) được truyền trong chuỗi. Bạn có thể đặt đối tượng sửa đổi này ở bất kỳ đâu trong hệ phân cấp và đối tượng sửa đổi này sẽ giao tiếp với các thực thể đối tượng sửa đổi cuộn lồng lên cây để có thể chia sẻ thông tin thông qua kênh này. Các khối xây dựng của đối tượng sửa đổi này là NestedScrollConnection và NestedScrollDispatcher.

NestedScrollConnection cung cấp một cách để phản hồi các giai đoạn của chu kỳ cuộn lồng và ảnh hưởng đến hệ thống cuộn lồng. Hệ thống này bao gồm 4 phương thức gọi lại, mỗi phương thức đại diện cho một trong các giai đoạn tiêu thụ: trước/sau khi cuộn và trước/sau khi vuốt:

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
    }
}

Mỗi lệnh gọi lại cũng cung cấp thông tin về delta đang được truyền: delta available cho giai đoạn cụ thể đó và delta consumed được tiêu thụ trong các giai đoạn trước. Nếu muốn ngừng truyền các delta lên hệ phân cấp tại bất kỳ thời điểm nào, bạn có thể sử dụng kết nối cuộn lồng để thực hiện việc này:

val disabledNestedScrollConnection = remember {
    object : NestedScrollConnection {
        override fun onPostScroll(
            consumed: Offset,
            available: Offset,
            source: NestedScrollSource
        ): Offset {
            return if (source == NestedScrollSource.SideEffect) {
                available
            } else {
                Offset.Zero
            }
        }
    }
}

Tất cả các lệnh gọi lại đều cung cấp thông tin về loại NestedScrollSource.

NestedScrollDispatcher khởi chạy chu kỳ cuộn lồng. Việc sử dụng trình điều phối và gọi các phương thức của trình điều phối sẽ kích hoạt chu kỳ. Các vùng chứa có thể cuộn có trình điều phối tích hợp sẵn, gửi các delta được ghi lại trong các cử chỉ vào hệ thống. Vì lý do này, hầu hết các trường hợp sử dụng tuỳ chỉnh thao tác cuộn lồng đều liên quan đến việc sử dụng NestedScrollConnection thay vì trình điều phối, để phản ứng với các delta đã tồn tại thay vì gửi các delta mới. Hãy xem NestedScrollDispatcherSample để biết thêm cách sử dụng.

Đổi kích thước hình ảnh khi cuộn

Khi người dùng cuộn, bạn có thể tạo hiệu ứng hình ảnh động, trong đó hình ảnh thay đổi kích thước dựa trên vị trí cuộn.

Đổi kích thước hình ảnh dựa trên vị trí cuộn

Đoạn mã này minh hoạ cách đổi kích thước hình ảnh trong LazyColumn dựa trên vị trí cuộn dọc. Hình ảnh sẽ thu nhỏ khi người dùng cuộn xuống và phóng to khi họ cuộn lên, vẫn nằm trong ranh giới kích thước tối thiểu và tối đa đã xác định:

@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
                }
        )
    }
}

Các điểm chính về mã

  • Mã này sử dụng NestedScrollConnection để chặn các sự kiện cuộn.
  • onPreScroll tính toán sự thay đổi về kích thước hình ảnh dựa trên delta cuộn.
  • Biến trạng thái currentImageSize lưu trữ kích thước hiện tại của hình ảnh, bị ràng buộc giữa minImageSize và maxImageSize. imageScale bắt nguồn từ currentImageSize.
  • LazyColumn bù dựa trên currentImageSize.
  • Đối tượng Image sử dụng đối tượng sửa đổi graphicsLayer để áp dụng tỷ lệ đã tính.
  • translationY trong graphicsLayer đảm bảo hình ảnh vẫn được căn giữa theo chiều dọc khi tỷ lệ thay đổi.

Kết quả

Đoạn mã trước đó tạo ra hiệu ứng hình ảnh thay đổi tỷ lệ khi cuộn:

Hình 8. Hiệu ứng hình ảnh thay đổi tỷ lệ khi cuộn.

Khả năng tương tác cuộn lồng

Khi cố gắng lồng các phần tử View cuộn được trong thành phần kết hợp có thể cuộn được hoặc ngược lại, bạn có thể gặp một vài sự cố. Sự cố đáng kể nhất sẽ xảy ra khi bạn cuộn thành phần con và tiếp cận ranh giới bắt đầu hoặc kết thúc, đồng thời mong đợi thành phần mẹ sẽ cuộn qua. Tuy nhiên, hành vi dự kiến này có thể không xảy ra hoặc có thể không hoạt động như mong đợi.

Vấn đề này là do việc kỳ vọng các thành phần kết hợp có thể cuộn được mà ra. Các thành phần kết hợp có thể cuộn đều có quy tắc "nested-scroll-by-default", nghĩa là bất kỳ vùng chứa nào có thể cuộn cũng đều phải tham gia vào chuỗi cuộn lồng, cả ở dạng thành phần mẹ thông qua NestedScrollConnection và thành phần con thông qua NestedScrollDispatcher. Sau đó thành phần con sẽ đẩy hoạt động cuộn lồng cho cha mẹ khi nó tiếp cận ranh giới. Ví dụ: quy tắc này cho phép Compose Pager và Compose LazyRow hoạt động hiệu quả cùng nhau. Tuy nhiên, khi thực hiện thao tác cuộn tương tác với ViewPager2 hoặc RecyclerView, vì các thao tác này không triển khai được NestedScrollingParent3 nên không thể cuộn liên tục từ thành phần con sang thành phần mẹ.

Để bật API tương tác cuộn lồng giữa các phần tử View có thể cuộn và thành phần kết hợp có thể cuộn đồng thời lồng theo cả hai hướng, bạn có thể sử dụng API tương tác có thể cuộn lồng để giảm thiểu những vấn đề này trong các trường hợp sau.

Thành phần mẹ View hợp tác có chứa một ComposeView con

Thành phần mẹ hợp tác View là một thành phần đã triển khai NestedScrollingParent3 và do đó có thể nhận các delta cuộn được từ thành phần kết hợp con hợp tác được lồng. ComposeView sẽ hoạt động như một thành phần con trong trường hợp này và sẽ cần triển khai (gián tiếp) NestedScrollingChild3. Một ví dụ về thành phần mẹ hợp tác là androidx.coordinatorlayout.widget.CoordinatorLayout.

Nếu cần khả năng tương tác cuộn lồng giữa vùng chứa thành phần mẹ cuộn được Viewparent và các thành phần kết hợp con có thể cuộn lồng, bạn có thể sử dụng rememberNestedScrollInteropConnection().

rememberNestedScrollInteropConnection() nhận và ghi nhớ NestedScrollConnection, cho phép bật khả năng tương tác cuộn lồng giữa một View mẹ có thể triển khai NestedScrollingParent3 và một Compose con. Bạn nên dùng thuộc tính này cùng với công cụ sửa đổi nestedScroll. Vì tính năng cuộn lồng được bật theo mặc định trên Compose nên bạn có thể sử dụng kết nối này để cho phép cả cuộn lồng từ View, đồng thời thêm logic kết dính cần thiết giữa Views và thành phần kết hợp.

Việc dùng CoordinatorLayout, CollapsingToolbarLayout và một thành phần kết hợp con là những trường hợp sử dụng thường xuyên, như trong ví dụ sau:

<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>

Trong Hoạt động hoặc Mảnh, bạn cần phải thiết lập thành phần kết hợp con và bắt buộc 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())
                        }
                    }
                }
            }
        }
    }
}

Thành phần kết hợp mẹ có thể chứa một AndroidView con

Tình huống này bao gồm việc triển khai API tương tác cuộn lồng ở phía Compose – khi bạn có một thành phần kết hợp mẹ chứa AndroidView con. AndroidView triển khai NestedScrollDispatcher, vì nó hoạt động như một phần tử con đối với thành phần mẹ đang cuộn trong Compose, cũng như NestedScrollingParent3 , vì nó hoạt động như một thành phần mẹ của View con đang cuộn. Compose cha mẹ sẽ có thể nhận được các delta cuộn lồng từ một View con có thể cuộn lồng.

Hãy xem ví dụ dưới đây để biết cách bạn có thể đạt được khả năng tương tác cuộn lồng trong tình huống này, cùng với thanh công cụ thu gọn 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) {
            // ...
        }
    }
    // ...
}

Ví dụ này cho thấy cách bạn có thể sử dụng API với công cụ sửa đổi 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)
                    }
            }
        )
    }
}

Sau cùng, ví dụ này cho thấy cách API tương tác cuộn lồng được sử dụng với BottomSheetDialogFragment để đạt được hành vi kéo và loại bỏ thành công:

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
        }
    }
}

Xin lưu ý rằng rememberNestedScrollInteropConnection() sẽ cài đặt a NestedScrollConnection trong phần tử mà bạn đính kèm vào. NestedScrollConnection chịu trách nhiệm truyền các delta từ cấp Compose sang cấp View. Điều này cho phép phần tử đó tham gia vào quá trình cuộn lồng, nhưng không cho phép tự động cuộn các phần tử. Đối với các thành phần kết hợp không thể tự động cuộn, chẳng hạn như Box hoặc Column, các delta cuộn trên những thành phần đó sẽ không truyền trong hệ thống cuộn lồng và không tiếp cận được NestedScrollConnection do rememberNestedScrollInteropConnection() cung cấp, theo đó các delta này sẽ không tiếp cận được thành phần mẹ View. Để giải quyết vấn đề này, hãy đảm bảo bạn cũng đặt công cụ sửa đổi có thể cuộn cho các loại thành phần kết hợp có thể cuộn được. Bạn có thể tham khảo phần trước về cuộn lồng để biết thêm thông tin chi tiết.

Chế độ xem cha mẹ không hợp tác View chứa một con ComposeView

Chế độ xem không hợp tác là chế độ không triển khai các giao diện NestedScrolling cần thiết ở phía View. Vui lòng lưu ý điều này có nghĩa là khả năng tương tác cuộn lồng với các Views này không hoạt động hiệu quả. Views không hợp tác là RecyclerView và ViewPager2.

Tài nguyên khác