Compatibilidade de entrada em telas grandes

Em dispositivos de tela grande, os usuários costumam interagir com apps usando um teclado, mouse, trackpad, stylus ou gamepad. Para permitir que o app aceite entrada de dispositivos externos, é preciso fazer o seguinte:

  • Testar o suporte básico do teclado, como a navegação usando as teclas Tab e de setas, Enter para confirmar a entrada de texto e a barra de espaço para reproduzir/pausar em apps de música.
  • Adicionar atalhos de teclado padrão, quando relevante. Por exemplo, Ctrl + Z para desfazer e Ctrl + S para salvar.
  • Testar interações básicas do mouse com o botão direito para acessar o menu de contexto, mudanças de ícone ao passar o cursor e eventos de rolagem da roda do mouse ou trackpad em visualizações personalizadas
  • Testar dispositivos de entrada específicos do app, como a stylus para apps de desenho, controles de jogos e controles de MIDI para apps de música.
  • Considerar o suporte avançado de entrada que pode destacar o app em ambientes de computador. Por exemplo, poder usar o touchpad como um modificador para apps de DJ, a captura de mouse para jogos, e mais atalhos de teclado para usuários focados nesse dispositivo.

Teclado

A maneira como seu app responde à entrada do teclado contribui para uma boa experiência em telas grandes. Existem três tipos de entrada de teclado: navegação, pressionamentos de tecla e atalhos.

A navegação pelo teclado raramente é implementada em apps voltados para o toque, mas os usuários esperam isso quando estão usando um app e estão com as mãos no teclado. Também pode ser essencial para usuários com necessidades de acessibilidade em smartphones, tablets, dispositivos dobráveis e dispositivos desktop.

Para muitos apps, a navegação simples pelas teclas de seta e Tab é o suficiente e é gerenciada automaticamente pelo framework do Android. Por exemplo, uma visualização de um Button é focalizável por padrão, e a navegação pelo teclado geralmente funciona sem precisar de mais código. Para ativar a navegação pelo teclado para visualizações não focalizáveis por padrão, os desenvolvedores precisam marcá-las como focalizáveis, o que pode ser feito de maneira programática ou em XML, conforme mostrado abaixo. Consulte Processamento de foco para ver mais informações.

Kotlin

yourView.isFocusable = true

Java

yourView.setFocusable(true);

Como alternativa, você pode definir o atributo focusable no arquivo de layout:

android:focusable="true"

Após a ativação do foco, o framework do Android cria um mapeamento de navegação para todas as visualizações focalizáveis com base na posição delas. Isso geralmente funciona conforme o esperado, e não é necessário realizar qualquer outra ação. Quando o mapeamento padrão não está correto para as necessidades de um app, ele pode ser substituído da seguinte maneira:

Kotlin

// Arrow keys
yourView.nextFocusLeftId = R.id.view_to_left
yourView.nextFocusRightId = R.id.view_to_right
yourView.nextFocusTopId = R.id.view_above
yourView.nextFocusBottomId = R.id.view_below

// Tab key
yourView.nextFocusForwardId = R.id.next_view

Java

// Arrow keys
yourView.setNextFocusLeftId(R.id.view_to_left);
yourView.setNextFocusRightId(R.id.view_to_left);
yourView.setNextFocusTopId(R.id.view_to_left);
yourView.setNextFocusBottomId(R.id.view_to_left);

// Tab key
yourView.setNextFocusForwardId(R.id.next_view);

É recomendável tentar acessar todas as funcionalidades do app antes de cada lançamento, usando apenas o teclado. Facilite o acesso às ações mais comuns sem precisar do mouse ou da entrada de toque.

A compatibilidade com o teclado pode ser essencial para usuários com necessidades de acessibilidade.

Pressionamentos de tecla

Para entrada de texto que seria processada por um teclado virtual na tela (IME), por exemplo, um EditText, os apps precisam se comportar como esperado em dispositivos de tela grande sem trabalho extra do desenvolvedor. Para pressionamentos de tecla que não podem ser previstos pelo framework, os apps precisam processar o comportamento sozinhos. Isso ocorre especialmente em apps com visualizações personalizadas.

Alguns exemplos são apps de chat que usam a tecla Enter para enviar uma mensagem, apps de música que iniciam/interrompem a reprodução com a tecla de espaço e jogos que controlam o movimento com as teclas w, a, s e d.

A maioria dos apps modifica o callback onKeyUp() e adiciona o comportamento esperado para cada código de tecla recebido, conforme mostrado abaixo:

Kotlin

override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
    return when (keyCode) {
        KeyEvent.KEYCODE_ENTER -> {
            sendChatMessage()
            true
        }
        KeyEvent.KEYCODE_SPACE -> {
            playOrPauseMedia()
            true
        }
        else -> super.onKeyUp(keyCode, event)
    }
}

Java

@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_ENTER) {
        sendMessage();
        return true;
    } else if (KeyEvent.KEYCODE_SPACE){
        playOrPauseMedia();
        return true;
    } else {
        return super.onKeyUp(keyCode, event);
    }
}

Um evento onKeyUp ocorre quando uma chave é liberada. O uso desse callback evita que os apps precisem processar vários eventos onKeyDown se uma tecla é pressionada ou liberada lentamente. Jogos e apps que querem saber quando uma tecla é pressionada ou que esperam que os usuários mantenham as teclas pressionadas no teclado, podem procurar o evento onKeyDown() e processar eventos onKeyDown repetidos.

Para ver mais informações sobre como oferecer compatibilidade com teclado, consulte Processar ações do teclado.

Atalhos

Os atalhos comuns Ctrl, Alt e Shift são esperados ao usar um teclado físico. Se um app não os implementa, a experiência dos usuários pode se tornar frustrante. Além disso, atalhos para tarefas específicas usadas com frequência agradam usuários avançados. Os atalhos facilitam o uso e a diferenciação de apps que não têm atalhos.

Alguns atalhos comuns incluem Ctrl + S (salvar), Ctrl + Z (desfazer) e Ctrl + Shift + Z (refazer). Para ver um exemplo de alguns atalhos mais avançados, consulte a lista de teclas de atalho do player de mídia VLC (link em inglês).

Os atalhos podem ser implementados usando dispatchKeyShortcutEvent(). Isso intercepta todas as combinações de tecla meta (Alt, Ctrl e Shift) para um determinado código de tecla. Para conferir se há uma tecla meta específica, use KeyEvent.isCtrlPressed(), KeyEvent.isShiftPressed(), KeyEvent.isAltPressed() ou KeyEvent.hasModifiers().

A separação do código de atalho de outro processo de pressionamento de tecla (como onKeyUp() e onKeyDown()) pode facilitar a manutenção do código e possibilita a aceitação padrão das teclas meta sem precisar implementar manualmente as verificações de tecla meta em todos os casos. Autorizar todas as combinações de tecla meta também pode ser mais conveniente para usuários acostumados com diferentes layouts de teclado e sistemas operacionais.

Kotlin

override fun dispatchKeyShortcutEvent(event: KeyEvent): Boolean {
  return when (event.keyCode) {
    KeyEvent.KEYCODE_O -> {
      openFile() // Ctrl+O, Shift+O, Alt+O
      true
    }
    KeyEvent.KEYCODE_Z-> {
      if (event.isCtrlPressed) {
        if (event.isShiftPressed) {
          redoLastAction() // Ctrl+Shift+Z pressed
          true
        } else {
          undoLastAction() // Ctrl+Z pressed
          true
        }
      }
    }
    else -> {
      return super.dispatchKeyShortcutEvent(event)
    }
  }
}

Java

@Override
public boolean dispatchKeyShortcutEvent(KeyEvent event) {
  if (event.getKeyCode() == KeyEvent.KEYCODE_O) {
      openFile(); // Ctrl+O, Shift+O, Alt+O
      return true;
  } else if(event.getKeyCode() == KeyEvent.KEYCODE_Z) {
      if (event.isCtrlPressed()) {
          if (event.isShiftPressed()) {
              redoLastAction();
              return true;
          }
          else {
              undoLastAction();
              return true;
          }
      }
  }
  return super.dispatchKeyShortcutEvent(event);
}

Você também pode implementar atalhos em onKeyUp() pela verificação de KeyEvent.isCtrlPressed(), KeyEvent.isShiftPressed() ou KeyEvent.isAltPressed() como explicado acima. Isso pode ser mais fácil de manter se o comportamento meta for mais uma modificação em um comportamento do app do que um atalho. Por exemplo, quando W significa "avançar" e Shift + W significa "avançar correndo".

Kotlin

override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
  return when(keyCode) {
    KeyEvent.KEYCODE_W-> {
      if (event.isShiftPressed) {
        if (event.isCtrlPressed) {
          flyForward() // Ctrl+Shift+W pressed
          true
        } else {
          runForward() // Shift+W pressed
          true
        }
      } else {
        walkForward() // W pressed
        true
      }
    }
    else -> super.onKeyUp(keyCode, event)
  }
}

Java

@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_W) {
        if (event.isShiftPressed()) {
            if (event.isCtrlPressed()) {
                flyForward(); // Ctrl+Shift+W pressed
                return true;
            } else {
                runForward(); // Shift+W pressed
                return true;
            }
        } else {
            walkForward();
            return true;
        }
    }
    return super.onKeyUp(keyCode, event);
}

Stylus

Muitos dispositivos de tela grande vêm com uma stylus, e os apps Android processam isso como entrada na tela touchscreen. Alguns dispositivos também podem ter uma prancheta de desenho USB ou Bluetooth, como o Wacom Intuos (link em inglês). Apps Android podem receber entradas por Bluetooth, mas não funcionam com a entrada USB.

Um evento da stylus é relatado como um evento de touchscreen via View.onTouchEvent() ou View.onGenericMotionEvent() e contém um MotionEvent.getSource() do tipo SOURCE_STYLUS.

O MotionEvent também contém outros dados:

Pontos históricos

O Android agrupa eventos de entrada e os envia uma vez por frame. Uma stylus pode relatar eventos em frequências muito maiores do que a tela. Ao criar apps de desenho, é importante conferir eventos que podem estar no passado recente usando as APIs getHistorical:

  • MotionEvent.getHistoricalX()
  • MotionEvent.getHistoricalY()
  • MotionEvent.getHistoricalPressure()
  • MotionEvent.getHistoricalAxisValue()

Rejeição da palma da mão

Quando os usuários desenham, escrevem ou interagem com o app usando uma stylus, às vezes, eles tocam na tela com a palma da mão. O evento de toque (definido como ACTION_DOWN ou ACTION_POINTER_DOWN), pode ser informado ao app antes que o sistema reconheça e desconsidere o toque acidental da palma da mão.

No Android, eventos de toque da palma da mão são cancelados pelo envio de um MotionEvent. Se o app receber ACTION_CANCEL, cancele o gesto. Se o app receber ACTION_POINTER_UP, verifique se FLAG_CANCELED está definido. Nesse caso, cancele o gesto.

Não verifique apenas o FLAG_CANCELED. A partir do Android 13, por conveniência, o sistema define FLAG_CANCELED para eventos ACTION_CANCEL, mas as versões anteriores não.

Android 12

No Android 12 (API de nível 32) e versões anteriores, a detecção da rejeição da palma da mão é possível apenas para eventos de toque de ponteiro único. Se um toque da palma for o único ponteiro, o sistema vai cancelar o evento definindo ACTION_CANCEL no objeto do evento de movimento. Se outros ponteiros estiverem inativos, o sistema vai definir ACTION_POINTER_UP, o que é suficiente para detectar a rejeição da palma da mão.

Android 13

No Android 13 (API de nível 33) e versões mais recentes, se um toque de palma da mão for o único ponteiro, o sistema vai cancelar o evento definindo ACTION_CANCEL e FLAG_CANCELED no objeto do evento de movimento. Se outros ponteiros estiverem inativos, o sistema vai definir ACTION_POINTER_UP e FLAG_CANCELED.

Sempre que o app receber um evento de movimento com ACTION_POINTER_UP, procure o FLAG_CANCELED para determinar se ele indica uma rejeição de palma da mão (ou outro cancelamento de eventos).

Apps de anotações

O ChromeOS tem uma intent especial que mostra apps de anotações registrados para os usuários. Para registrar um app como de anotações, adicione o seguinte ao manifesto do Android:

<intent-filter>
    <action android:name="org.chromium.arc.intent.action.CREATE_NOTE" />
    <category android:name="android.intent.category.DEFAULT" />
</intent-filter>

Quando um app é registrado, o usuário pode selecioná-lo como o app de anotações padrão. Quando uma nova anotação for solicitada, o app precisará criar uma nota vazia pronta para entrada da stylus. Quando o usuário quiser escrever em uma imagem (como uma captura de tela ou imagem salva), o app é iniciado com ClipData contendo um ou mais itens com URIs content://. O app precisa criar uma nota que use a primeira imagem anexada como uma imagem de plano de fundo e entrar em um modo em que o usuário pode desenhar na tela com uma stylus.

Testar intents de anotações sem uma stylus

Para testar se um app responde corretamente a intents de anotação sem uma stylus ativa, use o seguinte método para mostrar as opções de anotação no ChromeOS:

  1. Troque para o modo de desenvolvedor e torne o dispositivo gravável.
  2. Pressione Ctrl + Alt + F2 para abrir um terminal.
  3. Execute o comando sudo vi /etc/chrome_dev.conf.
  4. Pressione i para editar e adicionar --ash-enable-palette a uma nova linha no final do arquivo.
  5. Salve pressionando Esc e digitando :, w, q e pressionando Enter.
  6. Pressione Ctrl + Alt + F1 para retornar à interface normal do ChromeOS.
  7. Saia e faça login novamente.

Agora você vai ver um menu da stylus na estante:

  • Toque no botão da stylus na estante e escolha Nova nota. Isso vai abrir uma nota de desenho em branco.
  • Faça uma captura de tela. Na estante, selecione botão da stylus > Capturar tela ou faça o download de uma imagem. Você vai ver a opção "Anotar imagem" na notificação. O app vai iniciar com a imagem pronta para ser anotada.

Compatibilidade com mouse e touchpad

Geralmente, a maioria dos apps precisa processar apenas três eventos grandes com foco na tela: clicar com o botão direito do mouse, passar o cursor e arrastar e soltar.

Clicar com o botão direito

Todas as ações que fazem com que um app mostre um menu de contexto, por exemplo, tocar em um item de lista e pressionar, também precisam reagir a eventos de clique com o botão direito do mouse. Para gerenciar eventos de clique com o botão direito do mouse, os apps precisam registrar um View.OnContextClickListener. Para saber detalhes sobre como criar um menu de contexto, consulte Criação de menus de contexto.

Kotlin

yourView.setOnContextClickListener {
  showContextMenu()
  true
}

Java

yourView.setOnContextClickListener(v -> {
    showContextMenu();
    return true;
});

Passar cursor

Os desenvolvedores podem fazer com que seus layouts de apps pareçam bem-acabados e fáceis de usar pelo processamento de eventos de passar o cursor. Isso é especialmente verdadeiro para visualizações personalizadas. Os dois exemplos mais comuns são:

  • indicar aos usuários se um elemento tem comportamento interativo, clicável ou editável, mudando o ícone do ponteiro do mouse;
  • adicionar feedback visual a itens em uma grade ou lista grande quando o ponteiro do mouse estiver sobre eles.

Kotlin

// Change the icon to a "hand" pointer on hover,
// Highlight the view by changing the background.
yourView.setOnHoverListener { view, _ ->
  addVisualHighlighting(true)
  view.pointerIcon =
    PointerIcon.getSystemIcon(view.context,
    PointerIcon.TYPE_HAND)
  false // listener did not consume the event.
}

Java

yourView.setOnHoverListener((view, event) -> {
    addVisualHighlighting(true);
    view.setPointerIcon(PointerIcon
            .getSystemIcon(view.getContext(), PointerIcon.TYPE_HAND));
    return true;
});

Arrastar e soltar

Em um ambiente de várias janelas, os usuários esperam poder arrastar e soltar itens entre apps. Isso é válido para dispositivos desktop, tablets, smartphones e dispositivos dobráveis no modo de tela dividida.

Os desenvolvedores precisam considerar se os usuários vão arrastar itens para o app. Alguns exemplos comuns incluem: editores de fotos provavelmente recebem fotos, players de áudio recebem arquivos de áudio e programas de desenho recebem imagens.

Para oferecer suporte ao recurso de arrastar e soltar, siga a documentação desse recurso do Android e confira esta postagem do blog do ChromeOS (em inglês).

Considerações especiais para o ChromeOS

  • Não se esqueça de pedir permissão usando requestDragAndDropPermissions para acessar itens arrastados de fora do app.
  • Um item precisa ter a sinalização View.DRAG_FLAG_GLOBAL para ser arrastado para outros apps.

Compatibilidade avançada com ponteiro

Os apps que lidam com entradas avançadas de mouse e touchpad precisam seguir a documentação do Android para View.onGenericMotionEvent() e usar MotionEvent.getSource() para distinguir entre SOURCE_MOUSE e SOURCE_TOUCHSCREEN.

Examine o MotionEvent para implementar o comportamento necessário:

  • O movimento gera eventos de ACTION_HOVER_MOVE.
  • Os botões geram eventos de ACTION_BUTTON_PRESS e ACTION_BUTTON_RELEASE. Também é possível conferir o estado atual de todos os botões do mouse/trackpad usando getButtonState().
  • A rolagem da roda do mouse gera eventos ACTION_SCROLL.

Controles de jogos

Alguns dispositivos Android de tela grande oferecem suporte para até quatro controles de jogo. Para processar esses controles, os desenvolvedores precisam usar as APIs de controle de jogos padrão do Android. Consulte Compatibilidade com controles de jogos.

Os botões são mapeados para valores comuns após um mapeamento comum. Infelizmente, nem todos os fabricantes de controles de jogos seguem as mesmas convenções de mapeamento. Você pode fornecer uma experiência muito melhor aos usuários se permitir que eles selecionem entre os mapeamentos de controles mais usados. Consulte Processar o pressionamentos do botão do gamepad para saber mais.

Modo de tradução de entrada

O ChromeOS ativa um modo de tradução de entrada por padrão. Para a maioria dos apps Android, esse modo os ajuda a funcionar como esperado em um ambiente de computador. Alguns exemplos incluem ativar automaticamente a rolagem com dois dedos no touchpad, a rolagem da roda do mouse e o mapeamento de coordenadas de exibição brutas para coordenadas de janela. Geralmente, os desenvolvedores de apps não precisam implementar nenhum desses comportamentos.

Se um app implementar um comportamento de entrada personalizado, como, por exemplo, definir uma ação personalizada de gesto de pinça com dois dedos, ou se essas traduções de entrada não fornecerem os eventos de entrada esperados pelo app, você pode desativar o modo de tradução de entrada adicionando esta tag ao manifesto do Android:

<uses-feature
    android:name="android.hardware.type.pc"
    android:required="false" />

Outros recursos