1. Introdução
Assim que fica disponível para smartphones padrão, seu app também fica disponível para dispositivos de tela grande, como tablets, dobráveis e dispositivos ChromeOS.
Os usuários esperam que seu app ofereça uma experiência em telas grandes equivalente ou até melhor que a UX em telas pequenas.
Em dispositivos de tela grande, os usuários também têm mais chances de usar o app com um teclado físico e um dispositivo apontador, como um mouse ou trackpad. Alguns dispositivos de tela grande, como os Chromebooks, incluem um teclado físico e um dispositivo apontador. Outros se conectam a teclados e dispositivos apontadores por USB ou Bluetooth. Os usuários esperam usar teclados físicos e dispositivos apontadores para realizar as mesmas tarefas no seu app que fariam com touch screens.
Pré-requisitos
- Experiência na criação de apps com o Compose
- Conhecimento básico de Kotlin, incluindo lambdas e corrotinas
O que você vai criar
Vamos adicionar compatibilidade com teclado físico e mouse a um app baseado no Jetpack Compose. As etapas são estas:
- Seguir os critérios definidos nas diretrizes de qualidade de apps para telas grandes
- Analisar o resultado da auditoria e descobrir os problemas relacionados à compatibilidade com teclado físico e mouse
- Corrigir os problemas
Mais especificamente, você vai atualizar o app de exemplo com o seguinte:
- Navegação pelo teclado
- Atalho de teclado para rolar para baixo e para cima
- Auxiliar de atalhos do teclado
O que você vai aprender
- Como auditar seu app para identificar compatibilidade com dispositivos virtuais
- Como gerenciar a navegação pelo teclado com o Compose
- Como adicionar atalhos do teclado com o Compose
O que você precisa
- Android Studio Hedgehog ou mais recente
- Qualquer um destes dispositivos para executar o app de exemplo:
- Um dispositivo de tela grande com um teclado físico e um mouse
- Um Dispositivo virtual Android com um perfil definido na categoria de dispositivo desktop
2. Configurar
- Clone o repositório do GitHub para codelabs de tela grande:
git clone https://github.com/android/large-screen-codelabs
Como alternativa, você pode baixar e extrair o arquivo ZIP para codelabs de tela grande:
- Navegue até a pasta
add-keyboard-and-mouse-support-with-compose
. - No Android Studio, abra o projeto. A pasta
add-keyboard-and-cursor-support-with-compose
contém um projeto. - Se você não tem um tablet Android ou dispositivos dobráveis, ou um dispositivo ChromeOS com teclado físico e mouse, abra o Gerenciador de dispositivos no Android Studio e crie um dos dispositivos virtuais na categoria Desktop.
3. Explorar o app
O app de exemplo mostra uma lista de artigos. Os usuários poderão ler um artigo selecionado na lista.
O app atualiza o layout de maneira adaptativa de acordo com a largura da janela. Há três classes para classificar a largura da janela do app: compacta, média e expandida.
Layout para as classes de tamanho de janela compacta e média
O app usa um layout de painel único. Na tela inicial, o app mostra uma lista de artigos. Quando o usuário seleciona um artigo na lista, uma transição de tela acontece e o artigo é mostrado.
A navegação global é implementada com uma gaveta de navegação (link em inglês).
Layout para a classe de tamanho de janela expandida
O app usa um layout de detalhes e listas. O painel de listas mostra uma lista de artigos. O painel de detalhes mostra o artigo selecionado.
A navegação global é implementada com uma coluna de navegação (link em inglês).
4. Contexto
O Compose oferece várias APIs para ajudar o app a processar eventos do teclado físico e do mouse. Algumas APIs permitem o processamento de eventos de teclado e mouse de maneira semelhante ao processamento de eventos de toque. Como resultado, para muitos casos de uso, seu app é compatível com teclado físico e mouse sem nenhum esforço de desenvolvimento da sua parte.
Um exemplo típico é o modificador clickable
, que permite a detecção de cliques. Um toque com o dedo é detectado como um clique. Um clique do mouse e o pressionamento da tecla Enter
também são detectados como cliques. Se o app detecta cliques com o modificador clickable
, então também permite que os usuários interajam com componentes, independente do dispositivo de entrada.
No entanto, apesar do alto nível de compatibilidade das APIs, ainda é necessário trabalho de desenvolvimento para oferecer compatibilidade com teclado físico e mouse. Um dos motivos é a necessidade de descobrir os casos específicos testando seu app. Também é necessário algum esforço para reduzir o atrito do usuário decorrente das características dos dispositivos, como:
- Os usuários não sabem em quais componentes podem clicar
- Os usuários não conseguem mover o foco do teclado como esperado
- Os usuários não conseguem rolar para cima ou para baixo usando o teclado físico
Foco do teclado
O foco do teclado é a principal diferença entre a interação com o teclado físico e os toques na tela. Os usuários podem tocar em qualquer componente na tela, independente da posição do componente em que tocaram anteriormente. Por outro lado, com teclados, os usuários precisam selecionar o componente com que vão interagir antes do início da interação. A seleção é chamada de foco do teclado.
Os usuários podem mover o foco do teclado com a tecla Tab
e as teclas de direção (ou seta). O foco do teclado se move apenas para os componentes vizinhos por padrão.
A maior parte do atrito do teclado físico está relacionada ao foco do teclado. A lista abaixo mostra os problemas comuns:
- Os usuários não conseguem mover o foco do teclado para o componente com que querem interagir.
- O componente não detecta cliques quando os usuários pressionam a tecla
Enter
. - O foco do teclado se move de maneira diferente da expectativa do usuário.
- Os usuários precisam pressionar várias teclas para mover o foco do teclado para o componente com que querem interagir após as transições de tela.
- Os usuários não conseguem determinar qual componente está com o foco do teclado porque nenhum sinal visual indica isso.
- Os usuários não conseguem determinar o componente padrão em foco ao navegar para uma nova tela.
A indicação visual do foco do teclado é importante. Caso contrário, os usuários podem se perder no app e não entenderão o que acontece quando pressionam a tecla Enter
. O destaque é um sinal visual típico para indicar o foco do teclado. Os usuários podem perceber que o botão no card direito está em foco no teclado porque ele está em destaque.
Atalhos do teclado
Os usuários esperam poder usar atalhos de teclado comuns ao navegar no seu app com um teclado físico. Alguns componentes ativam atalhos de teclado comuns por padrão. BasicTextField
é um exemplo típico. Ele permite que os usuários utilizem atalhos de teclado padrão de edição de texto, incluindo o seguinte:
Atalho | Recurso |
| Copiar |
| Cortar |
| Colar |
| Desfazer |
| Refazer |
Seu app pode adicionar atalhos de teclado processando eventos de tecla. Os modificadores onKeyEvent
e onPreviewKeyEvent
permitem monitorar esses eventos.
Dispositivos apontadores: mouse, trackpad e stylus
O app pode processar mouse, trackpad e stylus da mesma maneira. Um toque no trackpad é detectado como um clique com o modificador clickable
. Um toque com a stylus também é detectado como um clique.
É importante que os usuários entendam visualmente se podem clicar em um componente ou não. É por isso que o estado de passar cursor é mencionado nas diretrizes de qualidade de apps para telas grandes.
Os componentes do Material3 são compatíveis com o estado de passar cursor por padrão. O Material 3 oferece o efeito visual do estado de passar cursor. Você pode aplicá-lo ao componente interativo com o modificador indication
.
Rolagens
Os contêineres roláveis são compatíveis com a rolagem da roda do mouse, gestos de rolagem no trackpad e rolagem com as teclas Page up
e Page down
por padrão.
Para a rolagem horizontal, seu app seria muito mais fácil de usar se mostrasse botões de seta para a esquerda e para a direita no estado de passar cursor, para que os usuários pudessem rolar o conteúdo clicando nos botões.
Mudanças de configuração por conexão e desconexão de dispositivos
Os usuários podem conectar ou desconectar um teclado e um mouse enquanto o app está em execução. Os usuários podem conectar um teclado físico quando encontrarem um campo para inserir uma grande quantidade de texto. Um mouse conectado por Bluetooth se desconecta quando entra no modo de espera. Um teclado conectado por USB pode ser desconectado por engano.
A conexão ou desconexão do hardware periférico aciona mudanças na configuração. O app precisa manter o estado durante as mudanças de configuração. Para mais informações, consulte Salvar estados da interface.
5. Testar o app de exemplo com teclado e mouse
Para começar o desenvolvimento de compatibilidade com teclado físico e mouse, inicie o app de exemplo e confirme o seguinte:
- Os usuários precisam conseguir mover o foco do teclado para todos os componentes interativos.
- Os usuários precisam poder "clicar" no componente em foco com a tecla
Enter
. - Os componentes interativos precisam mostrar uma indicação quando recebem o foco do teclado.
- O foco do teclado se move conforme o esperado pelo usuário (ou seja, de acordo com as convenções estabelecidas) usando a tecla
Tab
,Shift+Tab
e as teclas direcionais (seta). - Os componentes interativos precisam ter um estado de passar cursor.
- Os usuários precisam conseguir clicar nos componentes interativos.
- O menu de contexto aparece ao clicar com o botão direito do mouse (controle secundário) nos componentes apropriados, como aqueles em que o menu de contexto aparece com um toque longo ou uma seleção de texto.
Você precisará passar por todos os itens duas vezes neste codelab: uma para o layout de painel único e outra para o layout de detalhes e listas.
Problemas que precisam ser corrigidos neste codelab
Você vai encontrar problemas. Neste codelab, vamos corrigir o seguinte:
- Os usuários não conseguem ler o artigo inteiro apenas com o teclado físico porque não conseguem rolar a página para baixo.
- Os usuários não podem determinar se o painel de detalhes está com o foco do teclado ou não.
6. Permitir que os usuários leiam o artigo inteiro no painel de detalhes
O painel de detalhes mostra o artigo selecionado. Alguns artigos são muito longos para serem lidos sem rolar a tela. No entanto, os usuários não conseguem rolar para cima e para baixo no artigo apenas com o teclado físico.
Os contêineres roláveis, como LazyColumn
, permitem que os usuários rolem para baixo com a tecla Page down
. A causa raiz do problema é que os usuários não conseguem mover o foco do teclado para o painel de detalhes.
O componente deve ser capaz de obter o foco do teclado para receber um evento de teclado. O modificador focusable
permite que o componente modificado receba o foco do teclado.
Para corrigir esse problema, siga estas etapas:
- Acesse a função combinável
PostContent
no arquivoui/article/PostContent.kt
. - Modifique a função combinável
LazyColumn
com o modificadorfocusable
.
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun PostContent(
post: Post,
modifier: Modifier = Modifier,
contentPadding: PaddingValues = PaddingValues(0.dp),
state: LazyListState = rememberLazyListState(),
coroutineScope: CoroutineScope = rememberCoroutineScope(),
focusRequester: FocusRequester = remember { FocusRequester() },
header: (@Composable () -> Unit)? = null
) {
LazyColumn(
contentPadding = contentPadding,
modifier = modifier
.padding(horizontal = defaultSpacerSize)
.focusable(),
state = state,
) {
// Code to layout the selected article.
}
}
Indicar que o artigo tem foco do teclado
Agora, os usuários podem ler o artigo inteiro rolando a página para baixo com a tecla Page down
. No entanto, é difícil para eles entenderem se o componente PostContent
está com o foco do teclado ou não porque nenhum efeito visual indica isso.
O app pode indicar visualmente o foco do teclado associando uma Indication
a componentes. Uma indicação cria um objeto para renderizar efeitos visuais de acordo com as interações. Por exemplo, a indicação padrão do Material 3 destaca o componente quando ele tem o foco do teclado.
O app de exemplo tem uma Indication
com o nome BorderIndication
. A indicação mostra uma linha ao lado do componente com foco do teclado (como na captura de tela a seguir). O código é armazenado no arquivo ui/components/BorderIndication.kt
.
Para fazer com que o elemento combinável PostConent
mostre a BorderIndication
quando o foco do teclado estiver ativado, siga estas etapas:
- Acesse a função combinável
PostContent
no arquivoui/article/PostContent.kt
. - Declare o valor
interactionSource
associado ao valor de retorno da funçãoremember()
. - Chame a função
MutableInteractionSource()
na funçãoremember()
para que o objetoMutableInteractionSource
criado seja associado ao valorinteractionSource
. - Transmita o valor
interactionSource
ao modificadorfocusable
usando o parâmetro interactionSource. - Mude o modificador do elemento combinável
PostContent
para chamar o modificadorfocusable
após a invocação do modificadorindication
. - Transmita o valor
interactionSource
e o valor de retorno da funçãoBorderIndication
ao modificador da indicação.
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun PostContent(
post: Post,
modifier: Modifier = Modifier,
contentPadding: PaddingValues = PaddingValues(0.dp),
state: LazyListState = rememberLazyListState(),
coroutineScope: CoroutineScope = rememberCoroutineScope(),
focusRequester: FocusRequester = remember { FocusRequester() },
header: (@Composable () -> Unit)? = null
) {
val interactionSource = remember { MutableInteractionSource() }
LazyColumn(
contentPadding = contentPadding,
modifier = modifier
.padding(horizontal = defaultSpacerSize)
.indication(interactionSource, BorderIndication())
.focusable(interactionSource = interactionSource),
state = state,
) {
// Code to layout the selected article.
}
}
Adicionar atalhos de teclado para rolar para cima e para baixo
É um recurso comum permitir que os usuários rolem para cima e para baixo com a Spacebar
. Seu app pode implementar o recurso adicionando um atalho de teclado como a tabela abaixo:
Atalho | Recurso |
| Rolar o artigo para baixo |
| Rolar o artigo para cima |
O modificador onKeyEvent
permite que o app processe eventos de tecla que ocorrem em um componente modificado. O modificador usa uma lambda chamada com um objeto KeyEvent
que descreve o evento de tecla. A lambda precisa retornar um valor Boolean
indicando se o evento de tecla foi consumido.
A posição de rolagem de uma LazyColumn
e uma LazyRow
é capturada em um objeto LazyListState
. Seu app pode acionar a rolagem chamando o método de suspensão animateScrollBy()
no objeto LazyListState
. O método rola a LazyColumn
para baixo pelo número especificado de pixels. Quando a função de suspensão é chamada com um valor flutuante negativo, ela rola a tela para cima na LazyColumn
.
Para implementar esses atalhos de teclado, siga estas etapas:
- Acesse a função combinável
PostContent
no arquivoui/article/PostContent.kt
. - Modifique a função combinável
LazyColumn
com o modificadoronKeyEvent
. - Adicione uma expressão
if
à lambda transmitida ao modificadoronKeyEvent
desta forma:
- Retorne
true
se as condições abaixo forem atendidas: - A
Spacebar
for pressionada. Para detectar isso, teste se o atributotype
éKeyType.KeyDown
e o atributokey
éKey.Spacebar
. - O atributo
isCtrlPressed
é "false" (falso) para garantir que a teclaCtrl
não esteja pressionada. - O atributo
isAltPressed
é "false" para garantir que a teclaAlt
não esteja pressionada. - O atributo
isMetaPressed
é "false" para garantir que a teclaMeta
(consulte a observação) não esteja pressionada. - Caso contrário, retorne
false
.
- Determine a rolagem com o valor de
Spacebar
desta forma:
-0.4f
quando a teclaShift
é pressionada, o que é descrito pelo atributoisShiftPressed
do objetoKeyEvent
.- Caso contrário,
0.4f
.
- Chame o método
launch()
nocoroutineScope
, que é um parâmetro da função combinávelPostContent
. - Calcule a quantidade real de rolagem multiplicando o valor de rolagem relativo calculado na etapa anterior e o atributo
state.layoutInfo.viewportSize.height
no parâmetro lambda do métodolaunch
. O atributo representa a altura daLazyColumn
, chamada na função combinávelPostContent
. - Chame o método
state.animateScrollBy()
na lambda para o métodolaunch()
acionar a rolagem vertical.
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun PostContent(
post: Post,
modifier: Modifier = Modifier,
contentPadding: PaddingValues = PaddingValues(0.dp),
state: LazyListState = rememberLazyListState(),
coroutineScope: CoroutineScope = rememberCoroutineScope(),
focusRequester: FocusRequester = remember { FocusRequester() },
header: (@Composable () -> Unit)? = null
) {
val interactionSource = remember { MutableInteractionSource() }
LazyColumn(
contentPadding = contentPadding,
modifier = modifier
.padding(horizontal = defaultSpacerSize)
.onKeyEvent {
if (
it.type == KeyEventType.KeyDown &&
it.key == Key.Spacebar &&
!it.isCtrlPressed &&
!it.isAltPressed &&
!it.isMetaPressed
) {
val relativeAmount = if (it.isShiftPressed) {
-0.4f
} else {
0.4f
}
coroutineScope.launch {
state.animateScrollBy(relativeAmount * state.layoutInfo.viewportSize.height)
}
true
} else {
false
}
}
.indication(interactionSource, BorderIndication())
.focusable(interactionSource = interactionSource),
state = state,
) {
// Code to layout the selected article.
}
}
Informar os atalhos de teclado aos usuários
Os usuários não podem aproveitar ao máximo o teclado adicionado se não conhecerem os atalhos. O app pode informar aos usuários os atalhos disponíveis com o assistente de atalhos do teclado, que faz parte da interface do sistema Android. Os usuários podem abrir o assistente de atalhos com Meta+/
.
O app substitui o método onProvideKeyboardShortcuts()
na atividade principal para fornecer uma lista de atalhos para o assistente de atalhos do teclado.
Mais especificamente, o app fornece vários objetos KeyboardShortcutGroup
adicionando-os à lista mutável transmitida para onProvideKeyboardShortcuts()
. Cada KeyboardShortcutGroup
representa uma categoria nomeada de atalhos de teclado, o que permite que o app agrupe os atalhos disponíveis por finalidade ou contexto.
O app de exemplo tem dois atalhos de teclado, Spacebar
e Shift+Spacebar
.
Para disponibilizar esses dois atalhos no assistente, siga estas etapas:
- Abra o arquivo
MainActivity.kt
. - Substitua o método
onProvideKeyboardShortcuts()
emMainActivity
. - Confira se a versão do SDK do Android é o Android 7.0 (nível 24 da API) ou mais recente para que o assistente de atalhos de teclado esteja disponível.
- Confirme se o primeiro parâmetro do método não é
null
. - Crie um objeto
KeyboardShortcutInfo
para a teclaSpacebar
com os parâmetros abaixo:
- Texto de descrição
android.view.KeyEvent.KEYCODE_SPACE
0
(indica que não há modificadores)
- Crie outras
KeyboardShortcutInfo
paraShift+Spacebar
com os parâmetros abaixo:
- Texto de descrição
android.view.KeyEvent.KEYCODE_SPACE
android.view.KeyEvent.META_SHIFT_ON
- Crie uma lista imutável contendo os dois objetos
KeyboardShortcutInfo
. - Crie um objeto
KeyboardShortcutGroup
com os parâmetros abaixo:
- Nome do grupo no texto
- A lista imutável da etapa anterior
- Adicione o objeto
KeyboardShortcutGroup
à lista mutável transmitida como o primeiro parâmetro do métodoonProvideKeyboardShortcuts()
.
O método substituído tem esta aparência:
override fun onProvideKeyboardShortcuts(
data: MutableList<KeyboardShortcutGroup>?,
menu: Menu?,
deviceId: Int
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && data != null) {
val shortcutGroup = KeyboardShortcutGroup(
"To read articles",
listOf(
KeyboardShortcutInfo("Scroll down", KeyEvent.KEYCODE_SPACE, 0), // 0 means no modifier key is pressed
KeyboardShortcutInfo("Scroll up", KeyEvent.KEYCODE_SPACE, KeyEvent.META_SHIFT_ON),
)
)
data.add(shortcutGroup)
}
}
Executar
Agora, os usuários podem ler o artigo inteiro, mesmo rolando com a Spacebar
. Para testar, mova o foco do teclado para o artigo com a tecla Tab
ou teclas direcionais. Uma mensagem incentivando você a clicar na Spacebar
vai aparecer.
O assistente de atalhos do teclado mostra os dois atalhos que você adicionou (pressione Meta+/
). Os atalhos adicionados são listados na guia App atual.
7. Simplificar a navegação pelo teclado no painel de detalhes
Os usuários precisam pressionar a tecla Tab
várias vezes para mover o foco do teclado para o painel de detalhes quando o app estiver sendo executado na classe de tamanho de janela expandida. Com a tecla direcional direita, os usuários podem mover o foco do teclado da lista de artigos para o artigo com uma única ação, mas ainda precisam mover o foco do teclado. O foco inicial é incompatível com o objetivo principal do usuário, que é ler os artigos.
O app pode pedir para mover o foco do teclado para o componente específico com um objeto FocusRequester
. O modificador focusRequester
associa um objeto FocusRequester
ao componente modificado. Seu app pode enviar o pedido real para o movimento de foco chamando o método requestFocus()
do objeto FocusRequester
.
O envio de uma solicitação para mover o foco do teclado é um efeito colateral do componente. O app precisa chamar o método da maneira correta usando a função LaunchedEffect
.
Para definir o elemento combinável PostContent
para receber o foco do teclado quando os usuários selecionam um artigo na lista, siga estas etapas:
- Acesse a função combinável
PostContent
no arquivoui/article/
PostContent.kt
. - Associe o valor
focusRequester
à função combinávelLazyColumn
com o modificadorfocusRequester
. O valor focusRequester é especificado como um parâmetro opcional da função combinávelPostContent
. - Chame
LaunchedEffect
compost
, o primeiro parâmetro da função combinávelPostContent
, para que a lambda transmitida seja chamada quando o usuário selecionar um artigo. - Chame o método
focusRequester.requestFocus()
na lambda transmitida para a funçãoLaunchedEffect
.
O elemento combinável PostContent
atualizado fica assim:
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun PostContent(
post: Post,
modifier: Modifier = Modifier,
contentPadding: PaddingValues = PaddingValues(0.dp),
state: LazyListState = rememberLazyListState(),
coroutineScope: CoroutineScope = rememberCoroutineScope(),
focusRequester: FocusRequester = remember { FocusRequester() },
header: (@Composable () -> Unit)? = null
) {
val interactionSource = remember { MutableInteractionSource() }
LaunchedEffect(post) {
focusRequester.requestFocus()
}
LazyColumn(
contentPadding = contentPadding,
modifier = modifier
.padding(horizontal = defaultSpacerSize)
.onKeyEvent {
if (it.type == KeyEventType.KeyDown && it.key == Key.Spacebar) {
val relativeAmount = if (it.isShiftPressed) {
-0.4f
} else {
0.4f
}
coroutineScope.launch {
state.animateScrollBy(relativeAmount * state.layoutInfo.viewportSize.height)
}
true
} else {
false
}
}
.focusRequester(focusRequester),
.indication(interactionSource, BorderIndication())
.focusable(interactionSource = interactionSource),
state = state,
) {
// Code to layout the selected article.
}
}
Executar
Agora, o foco do teclado se move para o artigo quando os usuários escolhem um artigo da lista. A mensagem incentiva o uso de Spacebar
para rolar a tela para baixo ao escolher um artigo.
8. Parabéns
Muito bem! Você adicionou compatibilidade com teclado físico e mouse ao app de exemplo. Assim, os usuários podem selecionar e ler um artigo da lista usando apenas um teclado ou mouse físico.
Você aprendeu o seguinte para adicionar compatibilidade com teclado físico e mouse:
- Como conferir se o app é compatível com teclado físico e mouse, inclusive com um emulador
- Como gerenciar a navegação pelo teclado com o Compose
- Como adicionar atalhos do teclado com o Compose
Você também adicionou compatibilidade com teclado físico e mouse com uma pequena modificação no código.
Agora você já pode implementar o que aprendeu ao seu app de produção com o Compose.
E, com um pouco mais de aprendizado, você pode adicionar atalhos de teclado a estes recursos:
- Marcar o artigo selecionado como "Gostei".
- Adicionar o artigo selecionado aos favoritos.
- Compartilhar o artigo selecionado com outros apps.