Gestos com uma mão no Compose


No Wear OS 7 (nível 37 da API) e versões mais recentes, uma estrutura de gestos com uma mão, junto com uma API que faz parte do Compose para Wear OS, permite que os usuários interajam com o app sem tocar na tela.

Embora inicialmente tenha sido compatível com dispositivos Pixel Watch (Pixel Watch 3 e mais recentes), o framework está disponível para todos os OEMs. Ao adotar essa API, o suporte a gestos do seu app é escalonado automaticamente em todo o ecossistema à medida que o suporte a hardware aumenta.

Para ajudar os usuários a descobrir os gestos disponíveis sem poluir a interface, o framework do Wear OS oferece indicadores de gestos animados. Essas dicas visuais destacam onde um gesto pode ser realizado, enquanto o sistema gerencia automaticamente a cadência de exibição e a frequência de silenciamento de acordo com as preferências do usuário.

Gestos e ações compatíveis

A estrutura de gestos do Wear OS oferece suporte a dois tipos de gestos:

  • Ação principal (fazer gesto de pinça duas vezes): mapeia a ação principal em uma tela, como atender uma ligação ou alternar a reprodução de mídia.
  • Ação de dispensar (girar o pulso): mapeia a navegação para trás, dispensando uma caixa de diálogo ou cancelando um comando.

Configurar gestos no Compose

Embora a API de gestos com uma mão possa melhorar sua interface, é importante lembrar que alguns hardwares e OEMs não oferecem suporte a esses gestos. Se a API detectar que o app está sendo executado em um desses dispositivos não compatíveis, a biblioteca vai gerar uma operação nula automática sem afetar as interações de toque padrão.

Assim como nos comportamentos padrão do Compose, você ativa gestos com uma mão em elementos da interface usando modificadores. Você configura os gestos do app de acordo com a ação a ser realizada (principal ou dispensar) e um gestureId para coordenar com as preferências do usuário no nível do sistema, como a cadência de exibição de dicas e a frequência de silenciamento. Para expressar essa configuração, crie um objeto OneHandedGestureConfiguration. Recomendamos usar a função rememberOneHandedGestureConfiguration para isso. O OneHandedGestureConfiguration também é onde você pode fornecer a prioridade do gesto.

A função rememberOneHandedGestureConfiguration rastreia o histórico de interação do usuário em recomposições sem expor o estado do aplicativo. Depois que o app criar a configuração, ele vai transmitir a configuração para Modifier.oneHandedGesture no elemento combinável interativo.

Para ajudar os usuários a descobrir os gestos disponíveis, a biblioteca oferece o método OneHandedGestureClickIndicator. Esse método atua como um wrapper que substitui o conteúdo subjacente para indicar ao usuário que uma ação de gesto está disponível.

Componentes interativos

Para ativar gestos em um controle interativo, como um botão, crie uma configuração especificando OneHandedGestureAction.Primary e aplique o modificador oneHandedGesture. Transmita o mesmo MutableInteractionSource para o controle e o modificador para que os eventos de gestos emitam feedback de pressionamento visual no controle.

Para ativar o indicador de gesto, crie e lembre-se de uma instância de OneHandedGestureClickIndicatorState. Em seguida, para acionar o feedback visual, chame showIndicator no callback onGestureAvailable fornecido pelo modificador oneHandedGesture, que sinaliza ao sistema que um evento de indicação ocorreu. Depois de ser chamado, o componente substitui brevemente o conteúdo normal por uma animação de gesto.

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

Contêineres roláveis

Para telas ou listas roláveis, crie uma configuração especificando OneHandedGestureAction.Primary e aplique o modificador oneHandedGesture ao contêiner, chamando um auxiliar de rolagem, como scrollDown.

Para fornecer feedback visual para ações de rolagem, use o OneHandedGestureScrollIndicator. Esse componente funciona como um indicador de rolagem padrão que mostra a posição de rolagem, mas também pode indicar que um gesto de rolagem está disponível para o usuário. Esse indicador geralmente é transmitido ao slot scrollIndicator de um ScreenScaffold e é associado ao estado de um contêiner rolável, como um TransformingLazyColumn. Ele também observa um OneHandedGestureScrollIndicatorState para gerenciar as transições visuais.

Para acionar o feedback visual, chame showIndicator nesse estado, geralmente dentro do callback onGestureAvailable do modificador oneHandedGesture. Quando acionado, o indicador substitui temporariamente o estado visual padrão por uma sequência de animação de gestos para alertar o usuário.

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

Combinar vários gestos

É possível configurar um gesto de rolagem e um gesto de clique com a mesma ação principal adicionando gesturePriority ao objeto OneHandedGestureConfiguration:

  • OneHandedGesturePriority.Clickable (mais alto): atribua a controles interativos, como aqueles com tipo Button ou Card, para que eles capturem gestos quando estiverem visíveis na tela.
  • OneHandedGesturePriority.Scrollable (médio): atribua a contêineres roláveis ou pagináveis para que eles cedam a filhos clicáveis, mas rolem quando nenhum controle clicável estiver visível.
  • OneHandedGesturePriority.Unspecified (mais baixa): uma prioridade não atribuída. Esse é o valor padrão para um gesto que não tem um priority definido.

Ao definir explicitamente priority = OneHandedGesturePriority.Clickable em um botão interno e priority = OneHandedGesturePriority.Scrollable na lista principal, o sistema pode mostrar esse comportamento de prioridade de gesto. Quando o usuário aciona a ação principal com o gesto de uma mão, a lista rola para baixo até que o botão fique visível e, em seguida, captura a ação de clique do botão.

Testar e depurar gestos com o ADB

É possível testar gestos com uma mão em um dispositivo físico ou emulador sem fazer movimentos físicos do pulso usando o Android Debug Bridge (adb) e o serviço do sistema IWearGestureService.

Ativar simulação de gestos

Antes de simular gestos usando o ADB, configure as configurações do dispositivo e as substituições de restrições:

  1. Verifique se o dispositivo Wear OS está executando o Wear OS 7 (nível 37 da API) ou uma versão mais recente:

    adb shell getprop ro.build.version.sdk
    
  2. Se você estiver testando em um dispositivo físico que não está no pulso ou está em um carregador, substitua a restrição fora do corpo para que a estrutura de gestos permaneça ativa:

    adb shell cmd IWearGestureService override-constraints offbody-state
    

Acionar eventos de gestos usando o ADB

Para simular o gesto de fazer gesto de pinça duas vezes (que é a ação Primary em relógios Pixel), execute o seguinte comando do shell ADB:

adb shell cmd IWearGestureService gesture DoublePinch

Para simular o gesto de Girar o pulso (que é a ação Dismiss em relógios Pixel), execute o seguinte comando do shell ADB:

adb shell cmd IWearGestureService gesture WristTurn

Redefinir o rastreamento de dicas de gestos

O sistema rastreia o histórico de interação do usuário e mostra dicas de gestos flutuantes com base na configuração de cadência global (como Sempre ou Diariamente). Ao depurar os indicadores de gestos do app, redefina esse histórico de rastreamento para que as dicas apareçam novamente no seu pacote:

  • Em builds ou emuladores userdebug:

    adb shell cmd IWearGestureService hint clear <your_package_name>
    
  • Em builds de varejo (user):

    Em dispositivos comerciais sem acesso root, o hint clear é bloqueado pelas permissões do sistema. Limpe os dados locais do app para redefinir a descoberta de dicas:

    adb shell pm clear <your_package_name>
    

Restaurar restrições padrão

Para redefinir todas as substituições de restrição de depuração quando terminar o teste:

adb shell cmd IWearGestureService override-constraints reset

Resolver problemas de injeção de gestos

Se o app não receber gestos simulados:

  1. Verifique se a tela do relógio está ativa e LIGADA. A estrutura de gestos não envia gestos para aplicativos enquanto a tela está desligada ou no modo ambiente. Para ativar a tela usando o ADB, execute:

    adb shell input keyevent KEYCODE_WAKEUP
    
  2. Verifique se o app está registrado como um assinante de gestos ativo e se tem o foco da janela:

    adb shell cmd IWearGestureService get-active-gestures -readable
    

    Quando a tela de gestos do app está em primeiro plano e a tela está ativada, esse comando retorna [DoublePinch] ou [DoublePinch, WristTurn]. Se uma lista vazia ([]) for retornada, verifique se a janela está em foco ou se as restrições fora do corpo estão bloqueando a ativação.

  3. Inspecione o estado do serviço de gestos interno e os tokens de assinante ativos:

    adb shell dumpsys IWearGestureService
    

Outros recursos

Para orientações de design sobre quando e onde usar gestos com uma mão, consulte Gestos com uma mão.