Restaurar o foco

Ao navegar por interfaces do usuário complexas usando um teclado físico ou um botão direcional, os usuários costumam se mover entre diferentes seções da tela, como pular de uma coluna de navegação lateral para uma lista de conteúdo, interagir com um item e voltar. A restauração do foco permite que os contêineres se lembrem do elemento filho em foco anteriormente e restaurem o foco para ele quando o foco entrar novamente no contêiner.

Restaurar o foco em contêineres roláveis

No Jetpack Compose, contêineres roláveis, como LazyColumn, LazyRow e LazyVerticalGrid, atuam automaticamente como grupos de foco. Para ativar a restauração de foco em uma lista ou grade lenta, anexe o modificador focusRestorer:

LazyColumn(
    modifier = Modifier.focusRestorer()
) {
    items(items) { item ->
        Button(onClick = { /* Handle click */ }) {
            Text(item)
        }
    }
}

Quando o foco sai do LazyColumn, o contêiner registra qual item estava ativo. Quando o usuário volta para a lista, o foco é restaurado para esse item, em vez de reiniciar na parte de cima.

Restaurar o foco em Row e Column

Ao contrário dos contêineres de layout lento, os contêineres de layout padrão não roláveis, como Row e Column, não estabelecem um limite de foco por padrão. Como eles não interceptam eventos de entrada de foco por conta própria, aplicar apenas o modificador focusRestorer não é suficiente.

Para ativar a restauração de foco em Row ou Column, combine focusGroup com focusRestorer:

Row(
    modifier = Modifier
        .focusGroup()
        .focusRestorer()
) {
    Button(onClick = { /* Action 1 */ }) {
        Text("First item")
    }
    Button(onClick = { /* Action 2 */ }) {
        Text("Second item")
    }
    Button(onClick = { /* Action 3 */ }) {
        Text("Third item")
    }
}

Especificar um destino de foco substituto

Quando o foco entra em um contêiner pela primeira vez, não há um filho focado anteriormente. O Compose segue a ordem de foco padrão para determinar em qual elemento filho focar.

É possível personalizar qual filho recebe o foco na primeira entrada transmitindo um FocusRequester substituto para focusRestorer (ou uma lambda que retorna um FocusRequester em versões anteriores do Compose):

val firstItemRequester = remember { FocusRequester() }

LazyColumn(
    modifier = Modifier.focusRestorer(firstItemRequester)
) {
    itemsIndexed(items) { index, item ->
        val itemModifier = if (index == 0) {
            Modifier.focusRequester(firstItemRequester)
        } else {
            Modifier
        }
        Button(
            onClick = { /* Handle click */ },
            modifier = itemModifier
        ) {
            Text(item)
        }
    }
}

Se nenhum filho tiver sido focado anteriormente (ou se a restauração do foco falhar), o Compose vai mover o foco para o destino substituto associado.

Em componentes de navegação, como NavigationRail, NavigationBar, NavigationDrawer ou TabRow, os usuários esperam que o foco seja direcionado ao destino selecionado ao entrar no componente pela área de conteúdo.

É possível direcionar o foco para o destino ativo combinando Modifier.focusGroup() com focusProperties e definindo a propriedade onEnter (ou enter em versões anteriores do Compose):

val (homeRequester, searchRequester, settingsRequester) = remember { FocusRequester.createRefs() }

val selectedRequester = when (currentDestination) {
    "home" -> homeRequester
    "search" -> searchRequester
    "settings" -> settingsRequester
    else -> homeRequester
}

NavigationRail(
    modifier = modifier
        .focusProperties {
            // Redirect focus to the selected item when focus enters the navigation rail
            onEnter = { selectedRequester.requestFocus() }
        }
        .focusGroup()
) {
    NavigationRailItem(
        selected = currentDestination == "home",
        onClick = { onNavigate("home") },
        icon = { Icon(Icons.Default.Home, contentDescription = "Home") },
        label = { Text("Home") },
        modifier = Modifier.focusRequester(homeRequester)
    )
    NavigationRailItem(
        selected = currentDestination == "search",
        onClick = { onNavigate("search") },
        icon = { Icon(Icons.Default.Search, contentDescription = "Search") },
        label = { Text("Search") },
        modifier = Modifier.focusRequester(searchRequester)
    )
    NavigationRailItem(
        selected = currentDestination == "settings",
        onClick = { onNavigate("settings") },
        icon = { Icon(Icons.Default.Settings, contentDescription = "Settings") },
        label = { Text("Settings") },
        modifier = Modifier.focusRequester(settingsRequester)
    )
}

Restaurar o foco nas transições de tela

Quando um usuário clica em um item para navegar até uma tela de detalhes e depois retorna usando o botão "Voltar" ou gesto, é possível restaurar o foco para o item em que ele clicou.

Para preservar e restaurar o foco nas transições de tela:

  1. Associe o contêiner a um FocusRequester.
  2. Antes de acionar o evento de navegação, chame saveFocusedChild() no FocusRequester do contêiner.
  3. Quando o usuário voltar para a tela, solicite o foco inicial no contêiner ou chame restoreFocusedChild().