Tippen und drücken

Viele Composeables bieten integrierte Unterstützung für Tippen oder Klicken und enthalten eine onClick-Lambda-Funktion. Sie können beispielsweise ein anklickbares Surface erstellen, das alle für die Interaktion mit Oberflächen erforderlichen Material Design-Verhaltensweisen enthält:

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

Klicks sind jedoch nicht die einzige Möglichkeit, wie Nutzer mit Composeables interagieren können. Auf dieser Seite geht es hauptsächlich um Touch-Gesten, bei denen nur ein Zeiger verwendet wird und dessen Position für die Verarbeitung des Ereignisses nicht von Bedeutung ist. In der folgenden Tabelle sind diese Touch-Gesten aufgeführt:

Touch-Geste

Beschreibung

Tippen (oder klicken)

Der Cursor bewegt sich nach unten und dann nach oben.

Doppeltippen

Der Cursor bewegt sich nach unten, oben, unten und oben.

Lange drücken

Der Cursor bewegt sich nach unten und wird länger gehalten

Presse

Der Cursor bewegt sich nach unten.

Auf Antippen oder Klicken reagieren

clickable ist ein häufig verwendeter Modifikator, mit dem ein Composeable auf Tippen oder Klicks reagiert. Dieser Modifikator bietet auch zusätzliche Funktionen wie Unterstützung für den Fokus, das Bewegen der Maus und des Eingabestifts sowie eine anpassbare visuelle Anzeige, wenn er gedrückt wird. Der Modifikator reagiert auf „Klicks“ im weitesten Sinne des Wortes – nicht nur mit der Maus oder dem Finger, sondern auch auf Klickereignisse über die Tastatureingabe oder bei Verwendung von Bedienungshilfen.

Stellen Sie sich ein Raster mit Bildern vor, bei dem ein Bild im Vollbildmodus angezeigt wird, wenn ein Nutzer darauf klickt:

Sie können jedem Element im Raster den Modifikator clickable hinzufügen, um dieses Verhalten zu implementieren:

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

Der Modifikator clickable fügt außerdem zusätzliches Verhalten hinzu:

  • interactionSource und indication, die standardmäßig eine Welle zeichnen, wenn ein Nutzer auf das Kompositionenelement tippt. Weitere Informationen zum Anpassen finden Sie auf der Seite Nutzerinteraktionen verarbeiten.
  • Ermöglicht es Diensten für Barrierefreiheit, mit dem Element zu interagieren, indem die semantischen Informationen festgelegt werden.
  • Unterstützt die Interaktion über Tastatur oder Joystick, indem der Fokus gesetzt und auf Enter oder die Mitte des Steuerkreuzes gedrückt wird.
  • Achten Sie darauf, dass das Element anwählbar ist, damit es reagiert, wenn die Maus oder der Eingabestift darüber bewegt wird.

Langes Drücken, um ein Kontextmenü aufzurufen

Mit combinedClickable können Sie zusätzlich zum normalen Klickverhalten ein Doppeltippen oder langes Drücken hinzufügen. Mit combinedClickable können Sie ein Kontextmenü anzeigen lassen, wenn ein Nutzer ein Rasterbild antippt und gedrückt hält:

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

Als Best Practice sollten Sie haptisches Feedback einbinden, wenn der Nutzer Elemente lange drückt. Aus diesem Grund enthält das Snippet die performHapticFeedback-Aufrufmethode.

Ein Composeable durch Tippen auf einen Scrim schließen

In den obigen Beispielen fügen clickable und combinedClickable Ihren Composeables nützliche Funktionen hinzu. Sie zeigen eine visuelle Interaktionsanzeige, reagieren auf Mouseover-Ereignisse und unterstützen Fokus, Tastatur und Barrierefreiheit. Dieses zusätzliche Verhalten ist jedoch nicht immer wünschenswert.

Sehen wir uns den Bildschirm mit den Bilddetails an. Der Hintergrund sollte halbtransparent sein und der Nutzer sollte auf diesen Hintergrund tippen können, um den Detailbildschirm zu schließen:

In diesem Fall sollte dieser Hintergrund keine visuellen Interaktionshinweise haben, nicht auf das Bewegen des Mauszeigers reagieren, nicht fokussierbar sein und seine Reaktion auf Tastatur- und Barrierefreiheitsereignisse sollte sich von der eines typischen Composeables unterscheiden. Anstatt das Verhalten von clickable anzupassen, können Sie zu einer niedrigeren Abstraktionsebene wechseln und den Modifikator pointerInput direkt in Kombination mit der Methode detectTapGestures verwenden:

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

Als Schlüssel für den pointerInput-Modifikator übergeben Sie das onClose-Lambda. Dadurch wird das Lambda automatisch noch einmal ausgeführt, damit der richtige Rückruf aufgerufen wird, wenn der Nutzer auf den scrim tippt.

Zum Zoomen doppeltippen

Manchmal enthalten clickable und combinedClickable nicht genügend Informationen, um auf die Interaktion richtig zu reagieren. Beispielsweise benötigen Composables möglicherweise Zugriff auf die Position innerhalb der Grenzen des Composables, an der die Interaktion stattgefunden hat.

Sehen wir uns noch einmal den Bildschirm mit den Bilddetails an. Es empfiehlt sich, das Bild durch Doppeltippen heranzuzoomen:

Wie Sie im Video sehen, wird herangezoomt, wenn Sie auf die Karte tippen. Das Ergebnis ist unterschiedlich, wenn wir auf den linken oder rechten Teil des Bildes heranzoomen. Wir können den pointerInput-Modifikator in Kombination mit dem detectTapGestures verwenden, um die Tippposition in unsere Berechnung einzubeziehen:

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