Dotknij i naciśnij

Wiele komponentów ma wbudowane funkcje obsługi dotyku lub kliknięcia i obsługuje funkcję onClicklambda. Możesz na przykład utworzyć klikalny element Surface, który będzie uwzględniał wszystkie zachowania związane z Material Design odpowiednie do interakcji z powierzchniami:

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

Jednak kliknięcia nie są jedynym sposobem na interakcję z komponowanymi usługami. Ta strona skupia się na gestach, które wykorzystują jeden wskaźnik, a jego pozycja nie ma znaczenia dla obsługi danego zdarzenia. W tabeli poniżej znajdziesz te typy gestów:

Gest

Opis

Dotknij (lub kliknij)

Wskaźnik przesuwa się w dół, a potem w górę

Dwukrotne dotknięcie

Wskaźnik przesuwa się w dół, w górę, w dół i w górę

Długie naciśnięcie

Wskaźnik schodzi w dół i zostaje w tym położeniu przez dłuższy czas

Prasa

Wskaźnik schodzi w dół

Odpowiedź na dotknięcie lub kliknięcie

clickable to często używany modyfikator, który sprawia, że komponent reaguje na dotknięcia lub kliknięcia. Ten modyfikator dodaje też dodatkowe funkcje, takie jak obsługa fokusa, podświetlenie myszy i rysika oraz możliwość dostosowania wizualnego wskazania po naciśnięciu. Modyfikator reaguje na „kliknięcia” w najszerszym tego słowa znaczeniu – nie tylko za pomocą myszy lub palca, ale też zdarzeń kliknięcia za pomocą klawiatury lub podczas korzystania z usług ułatwień dostępu.

Wyobraź sobie siatkę obrazów, w której po kliknięciu obraz wyświetla się na pełnym ekranie:

Aby wdrożyć to zachowanie, możesz dodać modyfikator clickable do każdego elementu w kratce:

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

Modyfikator clickable wprowadza też dodatkowe działanie:

  • interactionSourceindication, które domyślnie wyświetlają efekt falowania, gdy użytkownik kliknie element kompozytywny. Dowiedz się, jak je dostosowywać na stronie Zarządzanie interakcjami z użytkownikami.
  • Umożliwia usługom ułatwień dostępu interakcję z elementem przez ustawienie informacji semantycznych.
  • Obsługuje interakcje za pomocą klawiatury lub joysticka, umożliwiając skupienie się na środku pada i naciśnięcie przycisku Enter.
  • Ustaw element tak, aby można było go podświetlić kursorem myszy lub rysika.

Przytrzymaj, aby wyświetlić menu kontekstowe

combinedClickable pozwala dodać działanie dwukrotnego kliknięcia lub długiego naciśnięcia oprócz normalnego kliknięcia. Gdy użytkownik dotknie i przytrzyma obraz siatki, możesz użyć combinedClickable, aby wyświetlić menu kontekstowe:

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

Sprawdzoną metodą jest uwzględnianie informacji zwrotnej haptycznej, gdy użytkownik naciska elementy przez dłuższy czas, dlatego fragment kodu zawiera wywołanie performHapticFeedback.

Zamknij kompozyt, klikając ekran zasłaniający

W powyższych przykładach komponenty clickablecombinedClickable dodają przydatne funkcje do komponentów składanych. Wyświetlają wizualne wskazanie interakcji, reagują na najechanie kursorem, obsługują fokus, klawiaturę i ułatwienia dostępu. Jednak takie dodatkowe działanie nie zawsze jest pożądane.

Przyjrzyjmy się ekranowi z informacjami o obrazie. Tło powinno być półprzezroczyste, a użytkownik powinien mieć możliwość kliknięcia tego tła, aby zamknąć ekran z informacjami:

W tym przypadku tło nie powinno zawierać żadnych wizualnych wskazówek dotyczących interakcji, nie powinno reagować na najechanie kursorem, nie powinno być możliwe do skoncentrowania, a jego reakcja na zdarzenia związane z klawiaturą i dostępnością powinna różnić się od reakcji typowego komponentu. Zamiast próbować dostosować zachowanie clickable, możesz przejść na niższy poziom abstrakcji i bezpośrednio użyć modyfikatora pointerInput w połączeniu z metodą detectTapGestures:

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

Jako klucz modyfikatora pointerInput przekazujesz lambda onClose. Ta funkcja automatycznie ponownie wykonuje funkcję lambda, aby upewnić się, że odpowiednia funkcja wywołania zwrotnego zostanie wywołana, gdy użytkownik kliknie ekran.

Kliknij dwukrotnie, aby powiększyć

Czasami clickablecombinedClickable nie zawierają wystarczającej ilości informacji, aby prawidłowo zareagować na interakcję. Na przykład komponenty mogą potrzebować dostępu do pozycji w ograniczeniach komponentu, w której miała miejsce interakcja.

Przyjrzyjmy się jeszcze raz ekranowi z informacjami o obrazie. Sprawdzoną metodą jest umożliwienie powiększania obrazu przez dwukrotne kliknięcie:

Jak widać na filmie, powiększanie następuje wokół pozycji zdarzenia dotknięcia. Wynik jest inny, gdy przybliżymy lewą część obrazu w porównaniu z prawą. Aby uwzględnić pozycję kliknięcia w obliczeniach, możemy użyć modyfikatora pointerInput w połączeniu z wartością detectTapGestures:

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