Mudar o comportamento de foco

À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 se concentrar em um determinado elemento combinável, solicitar o foco explicitamente em um deles; capturar ou soltar o foco ou redirecionar o foco na entrada ou saída. Isso descreve como alterar o comportamento de foco quando os padrões não são os mesmos que você precisam.

Ofereça uma navegação coerente com grupos focais

Às vezes, o Jetpack Compose não adivinha imediatamente o próximo item correto para navegação com guias, especialmente quando Composables principais complexas, como guias e listas entram em jogo.

Embora a pesquisa de foco geralmente siga a ordem de declaração do Composables, Isso é impossível em alguns casos, como quando um dos Composables na é uma hierarquia horizontal rolável que não é totalmente visível. Isso é mostrado no exemplo abaixo.

O Jetpack Compose pode decidir focar no próximo item mais próximo do início do como mostrado abaixo, em vez de continuar no caminho esperado navegação unidirecional:

Animação de um app mostrando uma navegação horizontal superior e uma lista de itens abaixo.
Figura 1. Animação de um app mostrando uma navegação horizontal superior e uma lista de itens abaixo

Neste exemplo, fica claro que os desenvolvedores não pretendiam que o foco pular da guia Chocolates para a primeira imagem abaixo e voltar para a Guia Pastéis. Em vez disso, eles queriam que o foco continuasse nas guias até que última guia e, em seguida, concentre-se no conteúdo interno:

Animação de um app mostrando uma navegação horizontal superior e uma lista de itens abaixo.
Figura 2. Animação de um app mostrando uma navegação horizontal superior e uma lista de itens abaixo

Em situações em que é importante que um grupo de elementos combináveis tenha foco sequencialmente, como na linha Tab do exemplo anterior, é necessário unir o Composable em um pai que tem 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 para o objeto direção, se um elemento de outro grupo estiver mais próximo do que um elemento não totalmente visível item no grupo atual, a navegação escolhe o mais próximo. Para evitar isso, comportamento, aplique o modificador focusGroup().

FocusGroup faz um grupo inteiro parecer uma única entidade em termos de foco. mas o grupo em si não terá o foco. Em vez disso, a criança mais próxima ganhar foco. Dessa forma, a navegação sabe que deve ir até o destino item antes de sair do grupo.

Nesse caso, as três instâncias de FilterChip serão focadas antes do SweetsCard itens, mesmo quando SweetsCards estão completamente visíveis para o usuário e alguns FilterChip podem estar ocultos. Isso acontece porque O modificador focusGroup instrui o gerenciador de foco a ajustar a ordem em que os itens estã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 estiver visível, coloque o foco a navegação a pegava por último. No entanto, adicionar esse modificador faz com que não pode ser descoberto, mas também terá foco logo após FilterChipB, conforme que 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 a ela. Se você quiser adicionar especificamente comportamento focalizável para 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")
}

Como tornar um elemento combinável não focalizável

Pode haver situações em que alguns dos seus elementos não devem participar no foco. Nessas raras ocasiões, você pode usar o canFocus property para impedir que uma Composable seja 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, você pode querer solicitar o foco explicitamente como uma resposta interação do usuário. Por exemplo, é possível perguntar a um usuário se ele quer reiniciar preencher um formulário e, se pressionarem "sim", mude o foco do primeiro campo dessa forma.

A primeira coisa a fazer é associar um objeto FocusRequester ao combinável para onde você quer mover o foco do teclado. No código a seguir, snippet, um objeto FocusRequester é associado a um TextField definindo um 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 é executado novamente a cada recomposição. O snippet a seguir mostra como solicitar que o sistema mova o foco do teclado quando o botão for clicou em:

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 o foco

Aproveite o foco para orientar seus usuários e fornecer os dados corretos ao seu app. para realizar a tarefa, por exemplo, conseguir um endereço de e-mail ou telefone válido número Embora os estados de erro informem os usuários sobre o que está acontecendo, você pode precisar que o campo com informações incorretas permaneça focado até que seja corrigidos.

Para capturar o foco, você pode invocar o método captureFocus() e Solte-o depois com o método freeFocus(), como no seguinte exemplo:

val textField = FocusRequester()

TextField(
    value = text,
    onValueChange = {
        text = it

        if (it.length > 3) {
            textField.captureFocus()
        } else {
            textField.freeFocus()
        }
    },
    modifier = Modifier.focusRequester(textField)
)

Precedência dos modificadores de foco

Modifiers podem ser vistas como elementos que têm apenas um filho. Portanto, quando você enfileira cada uma das Modifier à esquerda (ou na parte de cima) envolve a Modifier que segue a direita (ou abaixo). Isso significa que o segundo Modifier está dentro a primeira, de modo que, ao declarar dois focusProperties, apenas o um funciona, porque os seguintes estão contidos na parte superior.

Para esclarecer mais o conceito, consulte o código a seguir:

Modifier
    .focusProperties { right = item1 }
    .focusProperties { right = item2 }
    .focusable()

Nesse caso, a focusProperties que indica item2 como o foco correto não será usada, conforme consta no anterior; portanto, item1 vai ser um usado.

Com essa abordagem, o familiar responsável também pode redefinir o comportamento usando FocusRequester.Default:

Modifier
    .focusProperties { right = Default }
    .focusProperties { right = item1 }
    .focusProperties { right = item2 }
    .focusable()

O elemento pai não precisa fazer parte da mesma cadeia de modificadores. Um familiar responsável 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 todas as Modifier, as relacionadas ao foco se comportam de maneira diferente com base na ordem declará-las. Por exemplo, um código como o seguinte faz com que Box focalizável, mas a FocusRequester não está associada a ela, já que é declarado após o focalizável.

Box(
    Modifier
        .focusable()
        .focusRequester(Default)
        .onFocusChanged {}
)

É importante lembrar que um focusRequester está associado ao primeiro focalizável abaixo dele na hierarquia, de modo que esse focusRequester aponte para o primeiro filho focalizável. Caso nenhum esteja disponível, ele não apontará para nada. No entanto, como o Box é focalizável (graças ao modificador focusable()), você pode navegar até ele usando a navegação bidirecional.

Como outro exemplo, qualquer uma das opções a seguir funcionaria, já que o onFocusChanged() refere-se ao primeiro elemento focalizável que aparece após o 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 como mostrado na animação abaixo:

Animação de uma tela mostrando duas colunas de botões lado a lado e animando o foco de uma coluna para outra.
Figura 3. Animação de uma tela mostrando duas colunas de botões lado a lado e animando o foco de uma coluna para outra

Antes de nos aprofundarmos em como criar isso, é importante entender o padrão da pesquisa de foco. Sem modificações, quando a pesquisa de foco chega ao item Clickable 3, pressionando DOWN no botão direcional (ou equivalente tecla de seta) moveria o foco para o que fosse exibido abaixo de Column, para sair do grupo e ignorar o grupo da direita. Se não houver itens focalizáveis disponíveis, o foco não se move para nenhum lugar, mas permanece Clickable 3:

Para alterar esse comportamento e fornecer a navegação pretendida, você pode usar o Modificador focusProperties, que ajuda a gerenciar o que acontece quando o foco a pesquisa entra ou sai do 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 entra ou sai de uma certa parte da hierarquia, por exemplo, quando a interface tem duas colunas e quer ter certeza de que sempre que a primeira for processada, muda para o segundo:

Animação de uma tela mostrando duas colunas de botões lado a lado e animando o foco de uma coluna para outra.
Figura 4. Animação de uma tela mostrando duas colunas de botões lado a lado e animando o foco de uma coluna para outra.

Neste GIF, assim que o foco atinge o Clickable 3 Composable em Column 1, o próximo item em foco é Clickable 4 em outro Column. Esse comportamento pode ser alcançada combinando focusDirection com enter e exit valores dentro do modificador focusProperties. Ambos precisam de uma lambda que leve como parâmetro, a direção de onde vem o foco e retorna uma FocusRequester: Essa lambda pode se comportar de três maneiras diferentes: retornando FocusRequester.Cancel impede que o foco continue, enquanto FocusRequester.Default não muda o comportamento. Em vez disso, forneça o A FocusRequester anexada a outra Composable faz o foco pular para ela um Composable específico.

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 use o modificador onPreviewKey e implicar o LocalFocusManager para avançar o foco com o modificador moveFocus.

O exemplo a seguir mostra o comportamento padrão do mecanismo de foco: quando um Um pressionamento de tecla tab é detectado, o foco avança para o próximo elemento em foco lista. Embora isso não seja algo que você normalmente precise configurar, é importante saber o funcionamento interno do sistema para poder alterar o padrão do seu modelo.

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 a direção implícita no parâmetro da função.