Tippen und drücken

Viele Composables bieten integrierte Unterstützung für Berührungen oder Klicks und enthalten ein onClick-Lambda. Sie können beispielsweise ein anklickbares Surface erstellen, das alle Material Design-Verhaltensweisen enthält, die für die Interaktion mit Oberflächen geeignet sind:

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

Klicks sind jedoch nicht die einzige Möglichkeit, wie Nutzer mit Composables interagieren können. Auf dieser Seite geht es um Gesten, die einen einzelnen Zeiger umfassen, wobei die Position dieses Zeigers für die Verarbeitung dieses Ereignisses nicht relevant ist. In der folgenden Tabelle sind diese Arten von Touch-Gesten aufgeführt:

Touch-Geste

Beschreibung

Tippen (oder klicken) Sie auf

Mauszeiger bewegt sich nach unten und dann nach oben

Doppeltippen

Der Zeiger bewegt sich nach unten, oben, unten, oben.

Lange drücken

Der Zeiger wird nach unten bewegt und länger gehalten.

Presse

Zeiger bewegt sich nach unten

Auf Tippen oder Klicken reagieren

clickable ist ein häufig verwendeter Modifikator, mit dem ein Compose-Element auf Tippen oder Klicken reagiert. Dieser Modifikator bietet auch zusätzliche Funktionen, z. B. Unterstützung für Fokus, Maus- und Eingabestift-Hover sowie eine anpassbare visuelle Anzeige beim Drücken. Der Modifikator reagiert auf „Klicks“ im weitesten Sinne des Wortes – nicht nur mit der Maus oder dem Finger, sondern auch auf Klick-Events über die Tastatur oder bei Verwendung von Bedienungshilfen.

Stellen Sie sich ein Raster von 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 ein zusätzliches Verhalten hinzu:

  • interactionSource und indication, die standardmäßig einen Welleneffekt zeichnen, wenn ein Nutzer auf die komponierbare Funktion tippt. Informationen zum Anpassen dieser Einstellungen finden Sie auf der Seite Umgang mit Nutzerinteraktionen.
  • Ermöglicht Bedienungshilfen, mit dem Element zu interagieren, indem die Semantikinformationen festgelegt werden.
  • Unterstützt die Interaktion über Tastatur oder Joystick, indem der Fokus gesetzt und Enter oder die Mitte des Steuerkreuzes gedrückt wird.
  • Machen Sie das Element hoverbar, damit es auf das Bewegen der Maus oder des Eingabestifts darüber reagiert.

Lange drücken, um ein Kontextmenü einzublenden

Mit combinedClickable können Sie zusätzlich zum normalen Klickverhalten ein Verhalten für Doppeltippen oder langes Drücken hinzufügen. Mit combinedClickable können Sie ein Kontextmenü einblenden, wenn ein Nutzer ein Bild im Raster berührt und 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 }
    )
}

Es empfiehlt sich, haptisches Feedback zu geben, wenn der Nutzer lange auf Elemente drückt. Deshalb enthält das Snippet den Aufruf performHapticFeedback.

Composable durch Tippen auf eine Scrim schließen

In den obigen Beispielen fügen clickable und combinedClickable Ihren Composables nützliche Funktionen hinzu. Sie zeigen bei Interaktion eine visuelle Anzeige, reagieren auf Hover und bieten Unterstützung für Fokus, Tastatur und Bedienungshilfen. Dieses zusätzliche Verhalten ist jedoch nicht immer wünschenswert.

Sehen wir uns die Detailansicht des Bildes an. Der Hintergrund sollte halbtransparent sein und der Nutzer sollte auf den Hintergrund tippen können, um die Detailansicht zu schließen:

In diesem Fall sollte der Hintergrund bei Interaktionen keine visuelle Anzeige haben, nicht auf Hover reagieren, nicht fokussierbar sein und seine Reaktion auf Tastatur- und Bedienungshilfenereignisse sollte sich von der eines typischen Composables unterscheiden. Anstatt zu versuchen, das Verhalten von clickable anzupassen, können Sie eine niedrigere Abstraktionsebene verwenden 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 des pointerInput-Modifikators übergeben Sie das onClose-Lambda. Dadurch wird die Lambda-Funktion automatisch noch einmal ausgeführt, sodass der richtige Callback aufgerufen wird, wenn der Nutzer auf die Scrim tippt.

Zum Zoomen doppeltippen

Manchmal enthalten clickable und combinedClickable nicht genügend Informationen, um angemessen auf die Interaktion 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. Eine Best Practice ist es, das Heranzoomen des Bildes durch Doppeltippen zu ermöglichen:

Wie im Video zu sehen ist, wird um die Position des Tippereignisses herum herangezoomt. Das Ergebnis ist unterschiedlich, je nachdem, ob wir den linken oder den rechten Teil des Bildes heranzoomen. Wir können den Modifikator pointerInput in Kombination mit 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
        }
)