Chạm rồi nhấn

Nhiều thành phần kết hợp có tính năng hỗ trợ tích hợp cho thao tác nhấn hoặc nhấp và bao gồm một hàm lambda onClick. Ví dụ: bạn có thể tạo một Surface có thể nhấp, bao gồm tất cả hành vi Material Design phù hợp để tương tác với các khu vực:

Surface(onClick = { /* handle click */ }) {
    Text("Click me!", Modifier.padding(24.dp))
}

Tuy nhiên, lượt nhấp không phải là cách duy nhất mà người dùng có thể tương tác với các thành phần kết hợp. Trang này tập trung vào các cử chỉ liên quan đến một con trỏ duy nhất, trong đó vị trí của con trỏ đó không quan trọng đối với việc xử lý sự kiện đó. Bảng sau đây liệt kê các loại cử chỉ sau:

Cử chỉ

Mô tả

Nhấn (hoặc nhấp)

Con trỏ đi xuống rồi lên

Nhấn đúp

Con trỏ đi xuống, lên, xuống, lên

Nhấn và giữ

Con trỏ đi xuống và bị giữ lâu hơn

Hình ảnh trên báo chí

Con trỏ đi xuống

Phản hồi thao tác nhấn hoặc nhấp

clickable là một đối tượng sửa đổi thường dùng, giúp tạo phản ứng với các thao tác nhấn hoặc nhấp. Đối tượng sửa đổi này cũng bổ sung các tính năng bổ sung, chẳng hạn như hỗ trợ lấy nét, di chuột bằng chuột và bút cảm ứng, cũng như chỉ báo hình ảnh có thể tuỳ chỉnh khi được nhấn. Đối tượng sửa đổi phản hồi "lượt nhấp" theo nghĩa rộng nhất của từ này, không chỉ bằng chuột hoặc ngón tay, mà còn bằng các sự kiện nhấp chuột thông qua phương thức nhập bằng bàn phím hoặc khi sử dụng các dịch vụ hỗ trợ tiếp cận.

Hãy tưởng tượng một lưới hình ảnh, trong đó một hình ảnh hiển thị toàn màn hình khi người dùng nhấp vào hình ảnh đó:

Bạn có thể thêm đối tượng sửa đổi clickable vào từng mục trong lưới để triển khai hành vi này:

@Composable
private fun ImageGrid(photos: List<Photo>) {
    var activePhotoId by rememberSaveable { mutableStateOf<Int?>(null) }
    LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 128.dp)) {
        items(photos, { it.id }) { photo ->
            ImageItem(
                photo,
                Modifier.clickable { activePhotoId = photo.id }
            )
        }
    }
    if (activePhotoId != null) {
        FullScreenImage(
            photo = photos.first { it.id == activePhotoId },
            onDismiss = { activePhotoId = null }
        )
    }
}

Đối tượng sửa đổi clickable cũng thêm hành vi bổ sung:

  • interactionSourceindication, vẽ một hiệu ứng gợn sóng theo mặc định khi người dùng nhấn vào thành phần kết hợp. Tìm hiểu cách tuỳ chỉnh các sự kiện này trên trang Xử lý hoạt động tương tác của người dùng.
  • Cho phép các dịch vụ hỗ trợ tiếp cận tương tác với phần tử bằng cách đặt thông tin ngữ nghĩa.
  • Hỗ trợ tương tác bằng bàn phím hoặc cần điều khiển bằng cách cho phép lấy tiêu điểm và nhấn Enter hoặc giữa d-pad để tương tác.
  • Làm cho phần tử có thể di chuột để phần tử phản hồi với chuột hoặc bút cảm ứng di chuột qua phần tử đó.

Nhấn và giữ để mở trình đơn theo bối cảnh

combinedClickable cho phép bạn thêm hành vi nhấn đúp hoặc nhấn và giữ ngoài hành vi nhấp chuột thông thường. Bạn có thể sử dụng combinedClickable để hiện trình đơn theo bối cảnh khi người dùng chạm và giữ hình ảnh lưới:

var contextMenuPhotoId by rememberSaveable { mutableStateOf<Int?>(null) }
val haptics = LocalHapticFeedback.current
LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 128.dp)) {
    items(photos, { it.id }) { photo ->
        ImageItem(
            photo,
            Modifier
                .combinedClickable(
                    onClick = { activePhotoId = photo.id },
                    onLongClick = {
                        haptics.performHapticFeedback(HapticFeedbackType.LongPress)
                        contextMenuPhotoId = photo.id
                    },
                    onLongClickLabel = stringResource(R.string.open_context_menu)
                )
        )
    }
}
if (contextMenuPhotoId != null) {
    PhotoActionsSheet(
        photo = photos.first { it.id == contextMenuPhotoId },
        onDismissSheet = { contextMenuPhotoId = null }
    )
}

Cách tốt nhất là bạn nên thêm phản hồi xúc giác khi người dùng nhấn và giữ các thành phần. Đó là lý do đoạn mã bao gồm lệnh gọi performHapticFeedback.

Loại bỏ một thành phần kết hợp bằng cách nhấn vào màn hình

Trong các ví dụ trên, clickablecombinedClickable bổ sung chức năng hữu ích vào các thành phần kết hợp của bạn. Các mẫu này hiển thị chỉ báo trực quan khi tương tác, phản hồi thao tác di chuột và bao gồm dịch vụ hỗ trợ tiêu điểm, bàn phím và hỗ trợ tiếp cận. Tuy nhiên, hành vi bổ sung này không phải lúc nào cũng được mong muốn.

Hãy cùng xem màn hình thông tin chi tiết về hình ảnh. Nền phải là bán trong suốt và người dùng có thể nhấn vào nền đó để đóng màn hình chi tiết:

Trong trường hợp này, nền đó không được có bất kỳ chỉ báo trực quan nào khi tương tác, không phản hồi thao tác di chuột, không thể làm tâm điểm, đồng thời phản hồi của nền tảng đối với các sự kiện bàn phím và hỗ trợ tiếp cận khác với phản hồi của một thành phần kết hợp thông thường. Thay vì cố gắng điều chỉnh hành vi của clickable, bạn có thể thả xuống cấp độ trừu tượng thấp hơn và trực tiếp sử dụng đối tượng sửa đổi pointerInput kết hợp với phương thức detectTapGestures:

@OptIn(ExperimentalComposeUiApi::class)
@Composable
private fun Scrim(onClose: () -> Unit, modifier: Modifier = Modifier) {
    val strClose = stringResource(R.string.close)
    Box(
        modifier
            // handle pointer input
            .pointerInput(onClose) { detectTapGestures { onClose() } }
            // handle accessibility services
            .semantics(mergeDescendants = true) {
                contentDescription = strClose
                onClick {
                    onClose()
                    true
                }
            }
            // handle physical keyboard input
            .onKeyEvent {
                if (it.key == Key.Escape) {
                    onClose()
                    true
                } else {
                    false
                }
            }
            // draw scrim
            .background(Color.DarkGray.copy(alpha = 0.75f))
    )
}

Là khoá của đối tượng sửa đổi pointerInput, bạn truyền hàm lambda onClose. Thao tác này sẽ tự động thực thi lại lambda, đảm bảo lệnh gọi lại phù hợp sẽ được gọi khi người dùng nhấn vào màn hình.

Nhấn đúp để thu phóng

Đôi khi, clickablecombinedClickable không bao gồm đủ thông tin để phản hồi tương tác theo đúng cách. Ví dụ: thành phần kết hợp có thể cần quyền truy cập vào vị trí trong giới hạn của thành phần kết hợp nơi diễn ra hoạt động tương tác.

Hãy cùng xem lại màn hình thông tin chi tiết của hình ảnh. Phương pháp hay nhất là giúp bạn có thể phóng to hình ảnh bằng cách nhấn đúp:

Như bạn có thể thấy trong video, thao tác phóng to sẽ diễn ra xung quanh vị trí của sự kiện nhấn. Kết quả sẽ khác nhau khi chúng ta phóng to ở phần bên trái của hình ảnh so với phần bên phải. Chúng ta có thể sử dụng đối tượng sửa đổi pointerInput kết hợp với detectTapGestures để tích hợp vị trí nhấn vào phép tính của mình:

var zoomed by remember { mutableStateOf(false) }
var zoomOffset by remember { mutableStateOf(Offset.Zero) }
Image(
    painter = rememberAsyncImagePainter(model = photo.highResUrl),
    contentDescription = null,
    modifier = modifier
        .pointerInput(Unit) {
            detectTapGestures(
                onDoubleTap = { tapOffset ->
                    zoomOffset = if (zoomed) Offset.Zero else
                        calculateOffset(tapOffset, size)
                    zoomed = !zoomed
                }
            )
        }
        .graphicsLayer {
            scaleX = if (zoomed) 2f else 1f
            scaleY = if (zoomed) 2f else 1f
            translationX = zoomOffset.x
            translationY = zoomOffset.y
        }
)