Đố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 dọc đơn giản phản hồi các 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 vùng có thể cuộn

Đối tượng sửa đổi scrollableArea là một thành phần cơ bản để tạo vùng chứa có thể cuộn tuỳ chỉnh. Đối tượng này cung cấp một lớp trừu tượng cấp cao hơn so với đố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 để 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 danh sách cuộn tiêu chuẩn. Các thành phần cấp cao hơn 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 xây dựng bằng cách sử dụng scrollableArea.

Sự khác biệt giữa đố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 vật lý của thao tác đầ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 nội dung): delta được đảo ngược về mặt ngữ nghĩa để biểu thị sự 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 là ngược lại với chuyển động của con trỏ.

Hãy nghĩ như sau: 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 thành phần 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 vùng chứa có thể cuộn tiêu chuẩn.

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

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

delta được scrollable báo cáo cho dispatchRawDelta

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

Con trỏ di chuyển LÊN

Âm

Dương

Con trỏ di chuyển XUỐNG

Dương

Âm

Con trỏ di chuyển SANG TRÁI

Âm

Dương (Âm đối với RTL)

Con trỏ di chuyển SANG PHẢI

Dương

Âm (Dương đối với RTL)

(*) Lưu ý về dấu deltascrollableArea: Dấu của delta từ scrollableArea không chỉ là một sự đảo ngược đơn giản. Đối tượng 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 thao tác 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òn cắt nội dung theo giới hạn của bố cục và xử lý việc hiển thị hiệu ứng cuộn quá mức. Theo mặc định, đối tượ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 hiệu ứng 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.

Thời điểm sử dụng đối tượng 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 Tải từng phần không phục vụ đầy đủ. Điều này thường liên quan đến các 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 các hiệu ứng khác cho các thành phần con khi chú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 Tải từng phần hiển thị.

Tạo danh sách tuỳ chỉnh giống như bánh xe bằng scrollableArea

Mẫu sau đây minh hoạ cách sử dụng scrollableArea để tạo một danh sách dọc tuỳ chỉnh, trong đó các mục thu nhỏ khi di chuyển ra khỏi 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. Một danh sách dọc tuỳ chỉnh sử dụ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à nắm bắt các delta, nhưng không tự động bù trừ phần nội dung. Thay vào đó, thao tác này được uỷ quyền cho người dùng thông qua ScrollableState , đây là yêu cầu để đố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 phần tử 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.