Agrupar alvos de foco

É possível agrupar vários alvos de foco usando o modificador focusGroup. O agrupamento permite gerenciar hierarquias de foco, isolar regiões lógicas da interface (como barras de ferramentas, ações de caixa de diálogo ou listas de cards) e interceptar quando o foco entra ou sai de uma região.

Conceitos de grupo de discussão: entrada, saída e navegação padrão

Um grupo de foco estabelece um limite de navegação coeso ao redor dos destinos de foco filhos. Para entender como o foco se move nessa fronteira, é preciso conhecer dois conceitos principais:

  • Enter: o evento que ocorre quando o foco passa de um elemento fora do grupo de foco para um elemento dentro do grupo.
  • Saída: o evento que ocorre quando o foco sai do último elemento interno do grupo e faz a transição para um destino de foco externo.
Diagrama mostrando transições de entrada e saída em um limite de grupo de discussão.
Figura 1. Grupo de discussão com eventos de entrada e saída.

Comportamento padrão de transição de foco

Por padrão, quando um grupo de discussão recebe o foco de fora:

  1. Entrar no grupo: o Compose seleciona automaticamente uma meta de foco secundária interna. Para navegação unidimensional (Tab), ele seleciona o primeiro elemento filho na ordem de aparência visual. Para navegação bidimensional (tecla de seta ou D-pad), ele seleciona o filho geometricamente mais próximo da direção de entrada.
  2. Navegar no grupo: pressionar repetidamente as teclas de navegação move o foco entre os alvos irmãos incluídos no grupo.
  3. Sair do grupo: quando o usuário navega além do último destino de foco dentro do grupo, a próxima tecla pressionada sai do grupo e move o foco para o destino de foco vizinho fora do grupo.

Personalizar o redirecionamento de entrada de foco

Em muitos designs de interface, o destino de entrada padrão (como o primeiro filho na ordem de aparência) não é a opção mais lógica ou conveniente para o usuário.

Intenção e casos de uso

Considere uma caixa de diálogo de confirmação com um botão "Cancelar" à esquerda e um botão primário "Confirmar" à direita. Quando o usuário navega para baixo na barra de botões de um campo de formulário:

  • Comportamento padrão: o foco seria colocado em "Cancelar" porque é o primeiro botão na ordem de aparência visual.
  • Comportamento esperado: como "Confirmar" é a principal ação recomendada, talvez você queira que o foco vá direto para "Confirmar" ao entrar no grupo de botões no início da ordem de foco, mas ainda permitindo que o usuário pressione Esquerda para selecionar "Cancelar", se necessário.
Entrada de foco padrão em um grupo de botões.
Figura 2. Botões Cancelar e Confirmar com o destino de entrada de foco padrão.

Outros casos de uso comuns incluem:

  • Mover o foco diretamente para a guia selecionada ao inserir um TabRow.
  • Mover o foco diretamente para o controle "Reproduzir / Pausar" ao entrar em uma barra de reprodução de mídia.

Implementação com onEnter e FocusRequester

Para redirecionar o foco ao entrar em um grupo de discussão:

  1. Aplique o modificador focusGroup ao contêiner principal que envolve os alvos de foco relacionados.
  2. Aplique o modificador focusProperties ao contêiner e configure o callback onEnter.
  3. No callback onEnter (com escopo para FocusEnterExitScope), chame requestFocus() em um FocusRequester associado ao filho padrão preferido usando focusRequester.

val defaultChildRequester = remember { FocusRequester() }

Row(
    modifier = Modifier
        .focusGroup()
        .focusProperties {
            // Intercept entry into this group and redirect focus to the primary action
            onEnter = {
                defaultChildRequester.requestFocus()
            }
        }
) {
    Button(onClick = { /* Cancel action */ }) {
        Text("Cancel")
    }
    Button(
        onClick = { /* Confirm action */ },
        modifier = Modifier.focusRequester(defaultChildRequester)
    ) {
        Text("Confirm (Default)")
    }
}

Em onEnter e onExit, também é possível chamar cancelFocusChange() para bloquear completamente as transições de foco na fronteira ou inspecionar requestedFocusDirection para personalizar o roteamento com base em se o usuário navegou usando Tab, Down ou Left.

Contêineres roláveis são grupos de foco por padrão

No Jetpack Compose, contêineres roláveis, como LazyRow, LazyColumn e LazyVerticalGrid, atuam automaticamente como grupos de foco. Não é necessário aplicar explicitamente o modificador focusGroup para definir callbacks onEnter ou onExit.