Cuộn

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

Đối tượng sửa đổi verticalScrollhorizontalScroll cung cấp cách đơn giản nhất để người dùng cuộn một phần tử khi nội dung của phần tử đó lớn hơn giới hạn kích thước tối đa. Với phương thức sửa đổi verticalScrollhorizontalScroll, bạn không cần dịch hoặc bù trừ phần nội dung.

@Composable
private fun ScrollBoxes() {
    Column(
        modifier = Modifier
            .background(Color.LightGray)
            .size(100.dp)
            .verticalScroll(rememberScrollState())
    ) {
        repeat(10) {
            Text("Item $it", modifier = Modifier.padding(2.dp))
        }
    }
}

Một danh sách dọc đơn giản phản hồi các cử chỉ cuộn
Hình 1. Một danh sách theo chiều dọc đơn giản phản hồi cử chỉ cuộn.

ScrollState cho phép bạn thay đổi vị trí thanh cuộn hoặc xem trạng thái hiện tại của vị trí thanh cuộn. Để tạo lớp này với các tham số mặc định, hãy sử dụng rememberScrollState().

@Composable
private fun ScrollBoxesSmooth() {
    // Smoothly scroll 100px on first composition
    val state = rememberScrollState()
    LaunchedEffect(Unit) { state.animateScrollTo(100) }

    Column(
        modifier = Modifier
            .background(Color.LightGray)
            .size(100.dp)
            .padding(horizontal = 8.dp)
            .verticalScroll(state)
    ) {
        repeat(10) {
            Text("Item $it", modifier = Modifier.padding(2.dp))
        }
    }
}

Đối tượng sửa đổi cho khu vực có thể cuộn

Đối tượng sửa đổi scrollableArea là thành phần cơ bản để tạo vùng chứa có thể di chuyển tuỳ chỉnh. Thành phần này cung cấp một lớp trừu tượng cấp cao hơn cho đối tượng sửa đổi scrollable, xử lý các yêu cầu phổ biến như diễn giải delta cử chỉ, cắt nội dung và hiệu ứng cuộn quá mức.

Mặc dù scrollableArea được dùng cho các hoạt động triển khai tuỳ chỉnh, nhưng bạn thường nên ưu tiên các giải pháp có sẵn như verticalScroll, horizontalScroll hoặc các thành phần kết hợp như LazyColumn cho các danh sách cuộn tiêu chuẩn. Các thành phần cấp cao này đơn giản hơn cho các trường hợp sử dụng phổ biến và bản thân chúng được tạo bằng scrollableArea.

Sự khác biệt giữa các đối tượng sửa đổi scrollableAreascrollable

Sự khác biệt chính giữa scrollableAreascrollable nằm ở cách chúng diễn giải cử chỉ cuộn của người dùng:

  • scrollable (delta thô): Delta phản ánh trực tiếp chuyển động thực tế của dữ liệu đầu vào của người dùng (ví dụ: thao tác kéo con trỏ) trên màn hình.
  • scrollableArea (delta hướng nội dung): delta được đảo ngược về mặt ngữ nghĩa để biểu thị thay đổi đã chọn trong vị trí cuộn nhằm làm cho nội dung xuất hiện để di chuyển theo cử chỉ của người dùng, thường ngược với chuyển động của con trỏ.

Hãy nghĩ như thế này: scrollable cho bạn biết con trỏ đã di chuyển như thế nào, trong khi scrollableArea chuyển đổi chuyển động của con trỏ đó thành cách nội dung sẽ di chuyển trong một khung hiển thị có thể cuộn thông thường. Sự đảo ngược này là lý do khiến scrollableArea có cảm giác tự nhiên hơn khi triển khai một vùng chứa có thể cuộn tiêu chuẩn.

Bảng sau đây tóm tắt các dấu hiệu delta cho các trường hợp phổ biến:

Cử chỉ của người dùng

delta do scrollable báo cáo cho dispatchRawDelta

delta được scrollableArea*báo cáo cho dispatchRawDelta

Con trỏ di chuyển LÊN

Tiêu cực

Tích cực

Con trỏ di chuyển XUỐNG

Tích cực

Tiêu cực

Con trỏ di chuyển sang TRÁI

Tiêu cực

Tích cực (Tiêu cực đối với RTL)

Con trỏ di chuyển SANG PHẢI

Tích cực

Phủ định (Khẳng định đối với RTL)

(*) Lưu ý về scrollableAreadấu delta: Dấu delta từ scrollableArea không chỉ là một phép đảo ngược đơn giản. Công cụ này xem xét một cách thông minh:

  1. Hướng: Dọc hoặc ngang.
  2. LayoutDirection: LTR hoặc RTL (đặc biệt quan trọng đối với chế độ cuộn ngang).
  3. Cờ reverseScrolling: Hướng cuộn có bị đảo ngược hay không.

Ngoài việc đảo ngược delta cuộn, scrollableArea cũng cắt nội dung theo ranh giới của bố cục và xử lý việc kết xuất các hiệu ứng cuộn quá mức. Theo mặc định, hiệu ứng này sử dụng hiệu ứng do LocalOverscrollFactory cung cấp. Bạn có thể tuỳ chỉnh hoặc tắt chế độ này bằng cách sử dụng phương thức nạp chồng scrollableArea chấp nhận tham số OverscrollEffect.

Trường hợp sử dụng công cụ sửa đổi scrollableArea

Bạn nên sử dụng đối tượng sửa đổi scrollableArea khi cần tạo một thành phần cuộn tuỳ chỉnh mà đối tượng sửa đổi horizontalScroll hoặc verticalScroll hoặc bố cục Lazy không đáp ứng đủ. Điều này thường liên quan đến những trường hợp có:

  • Logic bố cục tuỳ chỉnh: Khi cách sắp xếp các mục thay đổi linh hoạt dựa trên vị trí cuộn.
  • Hiệu ứng hình ảnh độc đáo: Áp dụng các phép biến đổi, tỷ lệ hoặc hiệu ứng khác cho các thành phần con khi người dùng cuộn.
  • Kiểm soát trực tiếp: Cần kiểm soát chi tiết cơ chế cuộn ngoài những gì verticalScroll hoặc bố cục Lazy hiển thị.

Tạo danh sách tuỳ chỉnh dạng bánh xe bằng cách sử dụng scrollableArea

Mẫu sau đây minh hoạ cách dùng scrollableArea để tạo một danh sách dọc tuỳ chỉnh, trong đó các mục sẽ giảm tỷ lệ khi di chuyển ra xa trung tâm, tạo hiệu ứng hình ảnh "giống như bánh xe". Loại phép biến đổi phụ thuộc vào thao tác cuộn này là một trường hợp sử dụng hoàn hảo cho scrollableArea.

Hình 2. Danh sách dọc tuỳ chỉnh bằng scrollableArea.

@Composable
private fun ScrollableAreaSample() {
    // ...
    Layout(
        modifier =
            Modifier
                .size(150.dp)
                .scrollableArea(scrollState, Orientation.Vertical)
                .background(Color.LightGray),
        // ...
    ) { measurables, constraints ->
        // ...
        // Update the maximum scroll value to not scroll beyond limits and stop when scroll
        // reaches the end.
        scrollState.maxValue = (totalHeight - viewportHeight).coerceAtLeast(0)

        // Position the children within the layout.
        layout(constraints.maxWidth, viewportHeight) {
            // The current vertical scroll position, in pixels.
            val scrollY = scrollState.value
            val viewportCenterY = scrollY + viewportHeight / 2

            var placeableLayoutPositionY = 0
            placeables.forEach { placeable ->
                // This sample applies a scaling effect to items based on their distance
                // from the center, creating a wheel-like effect.
                // ...
                // Place the item horizontally centered with a layer transformation for
                // scaling to achieve wheel-like effect.
                placeable.placeRelativeWithLayer(
                    x = constraints.maxWidth / 2 - placeable.width / 2,
                    // Offset y by the scroll position to make placeable visible in the viewport.
                    y = placeableLayoutPositionY - scrollY,
                ) {
                    scaleX = scaleFactor
                    scaleY = scaleFactor
                }
                // Move to the next item's vertical position.
                placeableLayoutPositionY += placeable.height
            }
        }
    }
}
// ...

Đối tượng sửa đổi cho các mục cuộn được (scrollable)

Đối tượng sửa đổi scrollable khác với đối tượng sửa đổi thao tác cuộn ở chỗ scrollable phát hiện các cử chỉ cuộn và ghi lại các giá trị delta, nhưng không tự động bù trừ phần nội dung. Thay vào đó, quyền này được uỷ quyền cho người dùng thông qua ScrollableState. Đây là quyền bắt buộc để đối tượng sửa đổi này hoạt động chính xác.

Khi xây dựng ScrollableState, bạn phải cung cấp một hàm consumeScrollDelta. Hàm này sẽ được gọi trong mỗi bước cuộn (thông qua cử chỉ nhập, cuộn hoặc hất nhẹ) với delta được tính bằng pixel. Hàm này phải trả về khoảng cách đã cuộn để đảm bảo truyền sự kiện đúng cách trong trường hợp có các phần tử lồng nhau có đối tượng sửa đổi scrollable.

Đoạn mã sau đây phát hiện các cử chỉ và cho thấy một giá trị dạng số cho một phần bù trừ, nhưng không bù trừ phần tử nào:

@Composable
private fun ScrollableSample() {
    // actual composable state
    var offset by remember { mutableFloatStateOf(0f) }
    Box(
        Modifier
            .size(150.dp)
            .scrollable(
                orientation = Orientation.Vertical,
                // Scrollable state: describes how to consume
                // scrolling delta and update offset
                state = rememberScrollableState { delta ->
                    offset += delta
                    delta
                }
            )
            .background(Color.LightGray),
        contentAlignment = Alignment.Center
    ) {
        Text(offset.toString())
    }
}

Một thành phần trên giao diện người dùng phát hiện thao tác nhấn ngón tay và hiển thị giá trị dạng số cho vị trí của ngón tay
Hình 3. Một thành phần trên giao diện người dùng phát hiện thao tác nhấn ngón tay và hiển thị giá trị dạng số cho vị trí của ngón tay.