Às vezes, é necessário substituir o comportamento de foco padrão dos elementos na tela. Por exemplo, talvez você queira agrupar elementos combináveis, impedir o foco em um determinado elemento, solicitar foco em um deles, capturar ou liberar foco ou redirecionar o foco na entrada ou saída. Esta seção descreve como mudar o comportamento do foco quando os padrões não são o que você precisa.
Oferecer uma navegação coerente com grupos de discussão
Às vezes, o Jetpack Compose não adivinha imediatamente o próximo item correto para
a navegação com guias, especialmente quando Composables
pai complexo, como guias e
listas, entra em jogo.
Embora a pesquisa de foco normalmente siga a ordem de declaração do Composables
,
isso é impossível em alguns casos, como quando um dos Composables
na
hierarquia é um elemento rolável horizontal que não é totalmente visível. Isso é mostrado no exemplo abaixo.
O Jetpack Compose pode decidir focar o próximo item mais próximo do início da tela, conforme mostrado abaixo, em vez de continuar no caminho esperado para a navegação unidirecional:
Neste exemplo, está claro que os desenvolvedores não pretendiam que o foco fosse da guia Chocolates para a primeira imagem abaixo e, em seguida, voltasse para a guia Pastéis. Em vez disso, eles queriam que o foco continuasse nas guias até a última guia e, em seguida, se concentrasse no conteúdo interno:
Em situações em que é importante que um grupo de elementos combináveis receba foco
sequencialmente, como na linha da guia do exemplo anterior, você precisa envolver
Composable
em um pai que tenha o modificador focusGroup()
:
LazyVerticalGrid(columns = GridCells.Fixed(4)) { item(span = { GridItemSpan(maxLineSpan) }) { Row(modifier = Modifier.focusGroup()) { FilterChipA() FilterChipB() FilterChipC() } } items(chocolates) { SweetsCard(sweets = it) } }
A navegação bidirecional procura o elemento combinável mais próximo da direção
fornecida. Se um elemento de outro grupo estiver mais próximo de um item não totalmente visível
no grupo atual, a navegação escolherá o mais próximo. Para evitar esse
comportamento, você pode aplicar o modificador focusGroup()
.
FocusGroup
faz com que um grupo inteiro pareça uma única entidade em termos de foco,
mas o grupo em si não vai receber o foco. Em vez disso, o filho mais próximo
vai ganhar foco. Dessa forma, a navegação sabe ir até o item não totalmente visível
antes de sair do grupo.
Nesse caso, as três instâncias de FilterChip
serão focadas antes dos
itens SweetsCard
, mesmo quando a SweetsCards
estiver completamente visível para o
usuário e alguns FilterChip
puderem estar ocultos. Isso acontece porque o
modificador focusGroup
instrui o gerenciador de foco a ajustar a ordem em que os itens
são focados para que a navegação seja mais fácil e coerente com a interface.
Sem o modificador focusGroup
, se o FilterChipC
não estivesse visível, a navegação
de foco o selecionaria por último. No entanto, a adição desse modificador o torna não
apenas detectável, mas também recebe foco logo após FilterChipB
, como
os usuários esperam.
Como tornar um elemento combinável focalizável
Alguns elementos combináveis são focalizáveis por design, como um botão ou um elemento combinável com
o modificador clickable
anexado. Para adicionar especificamente
um comportamento focalizável a um elemento combinável, use o modificador focusable
:
var color by remember { mutableStateOf(Green) } Box( Modifier .background(color) .onFocusChanged { color = if (it.isFocused) Blue else Green } .focusable() ) { Text("Focusable 1") }
Tornar um elemento combinável fora de foco
Há situações em que alguns dos seus elementos não podem participar
do foco. Nessas raras ocasiões, você pode usar canFocus property
para excluir uma Composable
de ser focalizável.
var checked by remember { mutableStateOf(false) } Switch( checked = checked, onCheckedChange = { checked = it }, // Prevent component from being focused modifier = Modifier .focusProperties { canFocus = false } )
Solicitar foco do teclado com FocusRequester
Em alguns casos, pode ser necessário solicitar explicitamente o foco como uma resposta a uma interação do usuário. Por exemplo, você pode perguntar a um usuário se ele quer reiniciar o preenchimento de um formulário e, se ele pressionar "sim", você quer mudar o foco para o primeiro campo do formulário.
A primeira coisa a fazer é associar um objeto FocusRequester
ao
elemento combinável para que você quer mover o foco do teclado. No snippet de código
abaixo, um objeto FocusRequester
é associado a um TextField
definindo um
modificador chamado Modifier.focusRequester
:
val focusRequester = remember { FocusRequester() } var text by remember { mutableStateOf("") } TextField( value = text, onValueChange = { text = it }, modifier = Modifier.focusRequester(focusRequester) )
Você pode chamar o método requestFocus
do FocusRequester para enviar solicitações de foco reais. Invoque esse método fora de um contexto Composable
.
Caso contrário, ele será executado novamente a cada recomposição. O snippet abaixo
mostra como solicitar que o sistema mova o foco do teclado quando o botão for
clicado:
val focusRequester = remember { FocusRequester() } var text by remember { mutableStateOf("") } TextField( value = text, onValueChange = { text = it }, modifier = Modifier.focusRequester(focusRequester) ) Button(onClick = { focusRequester.requestFocus() }) { Text("Request focus on TextField") }
Capturar e liberar foco
É possível aproveitar o foco para orientar os usuários a fornecer os dados certos que o app precisa para executar a tarefa, por exemplo, conseguir um endereço de e-mail ou número de telefone válido. Embora os estados de erro informem aos usuários o que está acontecendo, pode ser necessário manter o foco do campo com informações incorretas até que ele seja corrigido.
Para capturar o foco, você pode invocar o método captureFocus()
e
liberá-lo depois com o método freeFocus()
, como no exemplo
a seguir:
val textField = FocusRequester() TextField( value = text, onValueChange = { text = it if (it.length > 3) { textField.captureFocus() } else { textField.freeFocus() } }, modifier = Modifier.focusRequester(textField) )
Precedência de modificadores de foco
Modifiers
podem ser vistos como elementos que têm apenas um filho. Portanto, ao colocá-los na fila, cada Modifier
à esquerda (ou de cima) encapsula a Modifier
seguinte à
direita (ou abaixo). Isso significa que a segunda Modifier
está dentro
da primeira. Assim, ao declarar dois focusProperties
, apenas o primeiro funciona, já que os seguintes estão contidos no primeiro.
Para esclarecer mais o conceito, consulte o código a seguir:
Modifier .focusProperties { right = item1 } .focusProperties { right = item2 } .focusable()
Nesse caso, o focusProperties
que indica item2
como o foco correto não
será usado, porque está contido no anterior. Portanto, item1
será
usado.
Aproveitando essa abordagem, um pai também pode redefinir o comportamento para o padrão
usando FocusRequester.Default
:
Modifier .focusProperties { right = Default } .focusProperties { right = item1 } .focusProperties { right = item2 } .focusable()
O pai não precisa fazer parte da mesma cadeia de modificadores. Um elemento
combinável pai pode substituir uma propriedade de foco de um elemento filho. Por exemplo,
considere este FancyButton
que torna o botão não focalizável:
@Composable fun FancyButton(modifier: Modifier = Modifier) { Row(modifier.focusProperties { canFocus = false }) { Text("Click me") Button(onClick = { }) { Text("OK") } } }
Um usuário pode tornar esse botão focalizável novamente, definindo canFocus
como true
:
FancyButton(Modifier.focusProperties { canFocus = true })
Como cada Modifier
, os relacionados ao foco se comportam de maneira diferente com base na ordem
que você os declara. Por exemplo, um código como o seguinte torna a Box
focável, mas a FocusRequester
não é associada a essa focalizável, já que
é declarada após a focalizável.
Box( Modifier .focusable() .focusRequester(Default) .onFocusChanged {} )
É importante lembrar que um focusRequester
está associado ao primeiro
foco abaixo dele na hierarquia. Portanto, esse focusRequester
aponta para o
primeiro filho focalizável. Caso não haja nenhuma disponível, nada acontecerá.
No entanto, como o Box
é focalizável (graças ao modificador focusable()
),
você pode navegar até ele usando navegação bidirecional.
Como outro exemplo, qualquer uma das opções abaixo funcionaria, já que o modificador onFocusChanged()
se refere ao primeiro elemento focalizável que aparece após os
modificadores focusable()
ou focusTarget()
.
Box( Modifier .onFocusChanged {} .focusRequester(Default) .focusable() ) |
Box( Modifier .focusRequester(Default) .onFocusChanged {} .focusable() ) |
Redirecionar o foco na entrada ou saída
Às vezes, você precisa fornecer um tipo muito específico de navegação, como o mostrado na animação abaixo:
Antes de aprender a criar esse comportamento, é importante entender o comportamento
padrão da pesquisa de foco. Sem qualquer modificação, quando a pesquisa de foco
chegar ao item Clickable 3
, pressionar DOWN
no botão direcional (ou a tecla de seta
equivalente) moverá o foco para o que for exibido abaixo da Column
,
saindo do grupo e ignorando o da direita. Se não houver
itens focalizáveis disponíveis, o foco não se moverá para nenhum lugar, mas permanecerá em
Clickable 3
.
Para mudar esse comportamento e oferecer a navegação pretendida, você pode usar o
modificador focusProperties
, que ajuda a gerenciar o que acontece quando a pesquisa
de foco entra ou sai da Composable
:
val otherComposable = remember { FocusRequester() } Modifier.focusProperties { exit = { focusDirection -> when (focusDirection) { Right -> Cancel Down -> otherComposable else -> Default } } }
É possível direcionar o foco para uma Composable
específica sempre que ela entrar
ou sair de uma determinada parte da hierarquia. Por exemplo, quando a interface tiver duas
colunas e você quiser garantir que, sempre que a primeira for processada,
o foco mude para a segunda:
Neste gif, assim que o foco atinge Clickable 3 Composable
na Column
1,
o próximo item em foco é Clickable 4
em outra Column
. Esse comportamento
pode ser alcançado combinando o focusDirection
com os valores enter
e exit
dentro do modificador focusProperties
. Ambos precisam de uma lambda que use
como parâmetro a direção de origem do foco e retorne uma
FocusRequester
. Essa lambda pode se comportar de três maneiras diferentes: retornar
FocusRequester.Cancel
impede que o foco continue, enquanto
FocusRequester.Default
não muda o comportamento. Em vez disso, fornecer o
FocusRequester
anexado a outro Composable
faz o foco pular para essa
Composable
específica.
Mudar a direção de avanço do foco
Para avançar o foco para o próximo item ou para uma direção precisa, você pode
aproveitar o modificador onPreviewKey
e sugerir o LocalFocusManager
para
avançar o foco com o modificador moveFocus
.
O exemplo abaixo mostra o comportamento padrão do mecanismo de foco: quando um
pressionamento de tecla tab
é detectado, o foco avança para o próximo elemento na lista
de foco. Embora isso não seja algo que você geralmente precisa configurar, é importante conhecer o funcionamento interno do sistema para poder mudar o comportamento padrão.
val focusManager = LocalFocusManager.current var text by remember { mutableStateOf("") } TextField( value = text, onValueChange = { text = it }, modifier = Modifier.onPreviewKeyEvent { when { KeyEventType.KeyUp == it.type && Key.Tab == it.key -> { focusManager.moveFocus(FocusDirection.Next) true } else -> false } } )
Neste exemplo, a função focusManager.moveFocus()
avança o foco para
o item especificado ou para a direção implícita no parâmetro da função.
Recomendados para você
- Observação: o texto do link aparece quando o JavaScript está desativado.
- Reaja ao foco
- Foco no Compose
- Mudar a ordem de travessia de foco