Tippen und drücken

Viele zusammensetzbare Funktionen haben eine integrierte Unterstützung für Tipp- oder Klickvorgänge und enthalten ein onClick-Lambda. Du kannst beispielsweise ein anklickbares Surface-Element erstellen, das alle Material Design-Verhaltensweisen umfasst, 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 zusammensetzbaren Funktionen interagieren können. Der Schwerpunkt dieser Seite liegt auf Gesten mit einem einzelnen Zeiger, bei denen die Position dieses Zeigers für die Verarbeitung des Ereignisses keine Rolle spielt. In der folgenden Tabelle sind diese Arten von Gesten aufgeführt:

Touch-Geste

Beschreibung

Tippen (oder klicken)

Zeiger nach unten und dann nach oben

Doppeltippen

Zeiger nach unten, oben, unten, oben

Lange drücken

Zeiger nach unten und für längere Zeit gedrückt halten

Drücken

Zeiger nach unten

Auf Tippen oder Klicken reagieren

clickable ist ein häufig verwendeter Modifikator, der eine zusammensetzbare Funktion auf Tipp- oder Klickvorgänge ermöglicht. Mit diesem Modifikator werden zusätzliche Funktionen hinzugefügt, z. B. können der Fokus, die Maus und der Eingabestift bewegt werden und es gibt eine anpassbare visuelle Anzeige, wenn darauf geklickt wird. Der Modifikator reagiert auf „Klicks“ im weitesten Sinne des Wortes – nicht nur mit der Maus oder dem Finger, sondern auch auf Klickereignisse über Tastatureingaben oder bei Verwendung von Bedienungshilfen.

Stellen Sie sich ein Bildraster vor, in dem ein Bild im Vollbildmodus angezeigt wird, wenn ein Nutzer darauf klickt:

Sie können den clickable-Modifikator jedem Element im Raster 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 clickable-Modifikator fügt außerdem zusätzliches Verhalten hinzu:

  • interactionSource und indication, die standardmäßig eine Welle darstellen, wenn ein Nutzer auf die zusammensetzbare Funktion tippt. Auf der Seite Nutzerinteraktionen verarbeiten erfahren Sie, wie Sie diese anpassen können.
  • Ermöglicht es Bedienungshilfen, mit dem Element zu interagieren, indem die Semantikinformationen festgelegt werden.
  • Unterstützt die Tastatur- oder Joystick-Interaktion durch fokussieren und Drücken von Enter oder der Mitte des Steuerkreuzes
  • Sie können das Element als Mouseover festlegen, damit es reagiert, wenn die Maus oder der Eingabestift darüber bewegt wird.

Lange drücken, um Kontextmenü einzublenden

Mit combinedClickable können Sie zusätzlich zum normalen Klickverhalten das Doppeltippen oder langes Drücken verwenden. Mit combinedClickable können Sie ein Kontextmenü anzeigen lassen, wenn ein Nutzer ein Rasterbild berührt und gedrückt hat:

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 einfügen, wenn der Nutzer lange auf Elemente drückt. Aus diesem Grund enthält das Snippet den Aufruf performHapticFeedback.

Du kannst eine zusammensetzbare Funktion schließen, indem du auf ein Gitter tippst

In den obigen Beispielen bieten clickable und combinedClickable Ihren zusammensetzbaren Funktionen nützliche Funktionen. Sie zeigen die Interaktion visuell an, reagieren auf Bewegen des Mauszeigers und bieten Unterstützung für Fokus, Tastatur und Bedienungshilfen. 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 der Hintergrund keine visuellen Hinweise auf die Interaktion haben, nicht auf Bewegen des Mauszeigers reagieren, nicht fokussierbar sein und seine Reaktion auf Tastatur- und Bedienungshilfen-Ereignisse unterscheidet sich von der einer typischen zusammensetzbaren Funktion. Anstatt zu versuchen, das Verhalten von clickable anzupassen, können Sie auf eine niedrigere Abstraktionsebene klicken und den pointerInput-Modifikator direkt in Kombination mit der Methode detectTapGestures verwenden:

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

Als Schlüssel des pointerInput-Modifikators übergibst du das Lambda onClose. Dadurch wird das Lambda automatisch noch einmal ausgeführt, sodass der richtige Callback aufgerufen wird, wenn der Nutzer auf das Scrim tippt.

Zum Zoomen doppeltippen

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

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

Wie Sie im Video sehen können, erfolgt das Heranzoomen um die Position des Tippereignisses. Das Ergebnis ist anders, wenn wir den linken und den rechten Teil des Bildes heranzoomen. Wir können den pointerInput-Modifikator 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
        }
)