Gestos con una mano con Compose


A partir de Wear OS 7 (nivel de API 37), un framework de gestos con una mano, junto con una API que forma parte de Compose para Wear OS, permite a los usuarios interactuar con tu app sin tocarla.

Si bien inicialmente se admitía en dispositivos Pixel Watch (Pixel Watch 3 y modelos posteriores), el framework está disponible para todos los OEMs. Cuando adoptas esta API, la compatibilidad con gestos de tu app se ajusta automáticamente en todo el ecosistema a medida que se expande la compatibilidad con hardware.

Para ayudar a los usuarios a descubrir los gestos disponibles sin desordenar la IU, el framework de Wear OS proporciona indicadores de gestos animados. Estas sugerencias visuales destacan dónde se puede realizar un gesto, mientras que el sistema administra automáticamente su cadencia de visualización y frecuencia de silenciamiento según las preferencias del usuario.

Gestos y acciones compatibles

El framework de gestos de Wear OS admite dos tipos de gestos:

  • Acción principal (doble pellizco): Se asigna a la acción principal en una pantalla, como responder una llamada o activar o desactivar la reproducción de contenido multimedia.
  • Acción de descartar (giro de muñeca): Se asigna a la navegación hacia atrás, el descarte de un diálogo o la cancelación de una instrucción.

Configura gestos en Compose

Si bien la API de gestos con una mano puede mejorar tu IU, es importante tener en cuenta que algunos OEMs y hardware no admiten estos gestos. Si la API detecta que tu app se ejecuta en uno de estos dispositivos no compatibles, la biblioteca no realiza ninguna operación automáticamente sin afectar las interacciones táctiles estándar.

Al igual que con los comportamientos estándar de Compose, puedes habilitar los gestos con una mano en los elementos de la IU con modificadores. Configuras los gestos de tu app según la acción que se realizará (principal o descartar) y un gestureId para coordinar con las preferencias del usuario a nivel del sistema, como la cadencia de visualización de sugerencias y el silenciamiento de frecuencia. Para expresar esta configuración, crea un OneHandedGestureConfiguration objeto. Te recomendamos que uses la rememberOneHandedGestureConfiguration función para crearlo. En OneHandedGestureConfiguration, también puedes proporcionar la prioridad del gesto.

La función rememberOneHandedGestureConfiguration realiza un seguimiento del historial de interacción del usuario en las recomposiciones sin exponer el estado de la aplicación. Una vez que tu app haya creado la configuración, debe pasarla a Modifier.oneHandedGesture en tu elemento componible interactivo.

Para ayudar a los usuarios a descubrir los gestos disponibles, la biblioteca proporciona el método OneHandedGestureClickIndicator. Este método actúa como un wrapper que reemplaza su contenido subyacente para indicarle al usuario que hay una acción de gesto disponible.

Componentes interactivos

Para habilitar gestos en un control interactivo, como un botón, crea una configuración que especifique OneHandedGestureAction.Primary y aplica el modificador oneHandedGesture. Pasa el mismo MutableInteractionSource al control y al modificador para que los eventos de gestos emitan comentarios visuales de presión en el control.

Para habilitar el indicador de gestos, crea y recuerda una instancia de OneHandedGestureClickIndicatorState. Luego, para activar los comentarios visuales, llama a showIndicator dentro de la devolución de llamada onGestureAvailable que proporciona el modificador oneHandedGesture, que le indica al sistema que se produjo un evento de indicación. Una vez que se llama, el componente reemplaza brevemente su contenido normal por una animación de gestos.

var isPlaying by remember { mutableStateOf(false) }
val onClick = { isPlaying = !isPlaying }

val gestureConfig = rememberOneHandedGestureConfiguration(
    action = OneHandedGestureAction.Primary
)
val indicatorState = remember { OneHandedGestureClickIndicatorState() }
val coroutineScope = rememberCoroutineScope()
val interactionSource = remember { MutableInteractionSource() }

Button(
    onClick = onClick,
    interactionSource = interactionSource,
    modifier = Modifier
        .fillMaxWidth()
        .oneHandedGesture(
            gestureConfiguration = gestureConfig,
            interactionSource = interactionSource,
            onGestureLabel = if (isPlaying) "pause" else "play",
            onGestureAvailable = { coroutineScope.launch { indicatorState.showIndicator() } },
            onGesture = onClick
        )
) {
    OneHandedGestureClickIndicator(
        gestureConfiguration = gestureConfig,
        state = indicatorState
    ) {
        Text(if (isPlaying) "Pause" else "Play", modifier = Modifier.fillMaxWidth())
    }
}

Contenedores desplazables

Para pantallas o listas desplazables, crea una configuración que especifique OneHandedGestureAction.Primary y aplica el modificador oneHandedGesture a tu contenedor, llamando a un asistente de desplazamiento, como scrollDown.

Para proporcionar comentarios visuales para las acciones de desplazamiento, puedes usar el OneHandedGestureScrollIndicator. Este componente funciona como un indicador de desplazamiento estándar que muestra la posición de desplazamiento, pero también puede indicar que hay un gesto de desplazamiento disponible para el usuario. Por lo general, este indicador se pasa a la ranura scrollIndicator de un ScreenScaffold y se vincula con el estado de un contenedor desplazable, como un TransformingLazyColumn. También observa un OneHandedGestureScrollIndicatorState para administrar sus transiciones visuales.

Para activar los comentarios visuales, llama a showIndicator en este estado, por lo general, dentro de la devolución de llamada onGestureAvailable del modificador oneHandedGesture. Una vez activado, el indicador reemplaza temporalmente su estado visual estándar por una secuencia de animación de gestos para alertar al usuario.

val scrollState = rememberTransformingLazyColumnState()
val gestureConfig = rememberOneHandedGestureConfiguration(
    action = OneHandedGestureAction.Primary,
    priority = OneHandedGesturePriority.Scrollable
)
val indicatorState = remember(gestureConfig) { OneHandedGestureScrollIndicatorState() }
val coroutineScope = rememberCoroutineScope()

ScreenScaffold(
    scrollState = scrollState,
    scrollIndicator = {
        OneHandedGestureScrollIndicator(
            gestureConfiguration = gestureConfig,
            indicatorState = indicatorState,
            scrollState = scrollState,
            modifier = Modifier.align(Alignment.CenterEnd)
        )
    }
) { contentPadding ->
    TransformingLazyColumn(
        state = scrollState,
        contentPadding = contentPadding,
        modifier = Modifier
            .fillMaxSize()
            .oneHandedGesture(
                gestureConfiguration = gestureConfig,
                onGestureLabel = "scroll",
                onGestureAvailable = {
                    coroutineScope.launch { indicatorState.showIndicator() }
                },
                onGesture = { OneHandedGestureDefaults.scrollDown(scrollState) }
            )
    ) {
        items(10) { index ->
            Text("Item $index", modifier = Modifier.padding(8.dp))
        }
    }
}

Combina varios gestos

Puedes configurar un gesto de desplazamiento y un gesto de clic con la misma acción principal agregando gesturePriority a tu objeto OneHandedGestureConfiguration:

  • OneHandedGesturePriority.Clickable (más alta): Asigna a controles interactivos, como aquellos con tipo Button o Card, para que capturen gestos cuando estén visibles en la pantalla.
  • OneHandedGesturePriority.Scrollable (media): Asigna a contenedores desplazables o paginables para que cedan a elementos secundarios en los que se puede hacer clic, pero se desplacen cuando no se vea ningún control en el que se pueda hacer clic.
  • OneHandedGesturePriority.Unspecified (más baja): Una prioridad no asignada. Este es el valor predeterminado para un gesto que no tiene un priority establecido.

Si estableces explícitamente priority = OneHandedGesturePriority.Clickable en un botón interno y priority = OneHandedGesturePriority.Scrollable en su lista superior, el sistema puede mostrar este comportamiento de prioridad de gestos. Cuando el usuario activa la acción principal con el gesto con una mano, primero desplaza la lista hacia abajo hasta que el botón esté visible. Luego, captura la acción de clic del botón.

Prueba y depura gestos con ADB

Puedes probar los gestos con una mano en un dispositivo físico o en un emulador sin realizar movimientos físicos de la muñeca con Android Debug Bridge (adb) y el servicio del sistema IWearGestureService.

Habilita la simulación de gestos

  1. Verifica que tu dispositivo Wear OS ejecute Wear OS 7 (nivel de API 37) y versiones posteriores.
  2. Si realizas pruebas en un dispositivo físico que no está en la muñeca o en un cargador, anula el estado del sensor fuera del cuerpo para que el dispositivo permanezca activo:
adb shell cmd sensorservice set-off-body-state 0

Activa eventos de gestos con ADB

Para simular el gesto de doble pellizco (que es la acción Primary en los relojes Pixel), ejecuta el siguiente comando de shell de ADB:

adb shell cmd IWearGestureService gesture 1

Para simular el gesto de giro de muñeca (que es la acción Dismiss en los relojes Pixel), ejecuta el siguiente comando de shell de ADB:

adb shell cmd IWearGestureService gesture 2

Restablece el seguimiento de sugerencias de gestos

El sistema realiza un seguimiento del historial de interacción del usuario y muestra sugerencias de gestos flotantes según la configuración de cadencia global (como Siempre o Diario). Cuando depures los indicadores de gestos de tu app, restablece este historial de seguimiento para que las sugerencias vuelvan a aparecer en tu paquete:

adb shell cmd IWearGestureService hint clear <your_package_name>

Para restablecer el estado del sensor fuera del cuerpo cuando termines de realizar pruebas, haz lo siguiente:

adb shell cmd sensorservice reset-off-body-state

Recursos adicionales

Para obtener orientación sobre el diseño de cuándo y dónde usar gestos con una mano, consulta Gestos con una mano.