Entrada por seletor giratório com o Compose

O termo "entrada por seletor giratório" se refere a ações feitas usando peças do relógio que giram. Em média, os usuários passam apenas alguns segundos interagindo com o relógio. É possível melhorar a experiência usando a entrada por seletor giratório para que os usuários possam realizar várias tarefas rapidamente.

As três principais fontes de entrada por seletor giratório na maioria dos relógios incluem o botão lateral giratório (RSB, na sigla em inglês) e uma borda física ou sensível ao toque, que é uma zona de toque circular ao redor da tela. Embora o comportamento esperado possa variar de acordo com o tipo de entrada, recomendamos que você ofereça suporte à entrada por seletor giratório para todas as interações essenciais.

Rolagem

A maioria dos usuários espera que os apps tenham suporte ao gesto de rolagem. À medida que o conteúdo passa pela tela, ofereça feedback visual aos usuários em resposta a interações pelo seletor giratório. O feedback visual pode incluir indicadores de posição para rolagem vertical ou indicadores de página.

Implemente a rolagem por seletor giratório usando o Compose para Wear OS. Este exemplo descreve um app com um scaffold e uma função ScalingLazyColumn com rolagem vertical. O scaffold fornece a estrutura de layout básica usada em apps para Wear OS e já tem um slot para um indicador de rolagem. Para mostrar o progresso da rolagem, crie um indicador de posição com base no objeto de estado da lista. As visualizações roláveis, incluindo ScalingLazyColumn, já têm um estado rolável para adicionar a entrada por seletor giratório. Para receber eventos de rolagem por seletor giratório, faça o seguinte:

  1. Solicite o foco explicitamente usando FocusRequester.
  2. Adicione o modificador onRotaryScrollEvent para interceptar eventos que o sistema gera quando um usuário gira a coroa ou a borda. Cada evento giratório tem a definição de um valor de pixel e de uma rolagem vertical ou horizontal. O modificador também tem um callback para indicar se o evento é consumido e, em caso positivo, interrompe a propagação de eventos para os pais.
val listState = rememberScalingLazyListState()

Scaffold(
    positionIndicator = {
        PositionIndicator(scalingLazyListState = listState)
    }
) {

    val focusRequester = rememberActiveFocusRequester()
    val coroutineScope = rememberCoroutineScope()

    ScalingLazyColumn(
        modifier = Modifier
            .onRotaryScrollEvent {
                coroutineScope.launch {
                    listState.scrollBy(it.verticalScrollPixels)

                    listState.animateScrollBy(0f)
                }
                true
            }
            .focusRequester(focusRequester)
            .focusable(),
        state = listState
    ) { ... }
}

Valores discretos

Use também interações giratórias para ajustar valores discretos, como mudar o brilho nas configurações ou selecionar os números no seletor de horário ao definir um alarme.

Assim como ScalingLazyColumn, o seletor, o controle deslizante, o stepper e outros elementos combináveis precisam de foco para receber a entrada por seletor giratório. No caso de vários destinos roláveis na tela, como as horas e os minutos no seletor de horário, você precisará criar um FocusRequester para cada destino e processar a mudança de foco conforme necessário quando o usuário tocar nas horas ou nos minutos.

@Composable
fun TimePicker() {
    var selectedColumn by remember { mutableStateOf(0) }
    val focusRequester1 = remember { FocusRequester() }
    val focusRequester2 = remember { FocusRequester() }

    Row {
       Picker(...)
       Picker(...)
    }

    LaunchedEffect(selectedColumn) {
        listOf(focusRequester1,
               focusRequester2)[selectedColumn]
            .requestFocus()
    }
}

Ações personalizadas

Também é possível criar ações personalizadas que respondam à entrada por seletor giratório no app. Por exemplo, use a entrada por seletor giratório para aumentar e diminuir o zoom ou controlar o volume em um app de música.

Se o componente não tiver suporte nativo a eventos de rolagem, como o controle de volume, você poderá processar esses eventos por conta própria.

// VolumeScreen.kt

val focusRequester: FocusRequester = remember { FocusRequester() }

Column(
    modifier = Modifier
        .fillMaxSize()
        .onRotaryScrollEvent {
            // handle rotary scroll events
            true
        }
        .focusRequester(focusRequester)
        .focusable(),
) { ... }

Crie um estado personalizado gerenciado no modelo de visualização e um callback individualizado que seja usado para processar eventos de rolagem por seletor giratório.

// VolumeViewModel.kt

object VolumeRange(
    public val max: Int = 10
    public val min: Int = 0
)

val volumeState: MutableStateFlow<Int> = ...

fun onVolumeChangeByScroll(pixels: Float) {
    volumeState.value = when {
        pixels > 0 -> min (volumeState.value + 1, VolumeRange.max)
        pixels < 0 -> max (volumeState.value - 1, VolumeRange.min)
    }
}

Para simplificar, o exemplo anterior usa valores de pixel que, se usados de fato, serão muito sensíveis.

Use o callback quando receber os eventos, conforme mostrado no snippet a seguir.

val focusRequester: FocusRequester = remember { FocusRequester() }
val volumeState by volumeViewModel.volumeState.collectAsState()

Column(
    modifier = Modifier
        .fillMaxSize()
        .onRotaryScrollEvent {
            volumeViewModel
                .onVolumeChangeByScroll(it.verticalScrollPixels)
            true
        }
        .focusRequester(focusRequester)
        .focusable(),
) { ... }

Outros recursos

Considere usar o Horologist, um projeto de código aberto do Google que disponibiliza um conjunto de bibliotecas do Wear que complementam a funcionalidade do Compose para Wear OS e outras APIs do Wear OS. O Horologist fornece uma implementação para casos de uso avançados e vários detalhes específicos dos dispositivos.

Por exemplo, a sensibilidade de diferentes origens de entrada por seletor giratório pode variar. Para que a transição seja mais suave entre os valores, é possível usar a limitação de taxa ou adicionar ajustes ou animações. Isso deixa a velocidade mais natural para os usuários. O Horologist inclui modificadores para componentes roláveis e para valores discretos. Ele também inclui utilitários para processar o foco e uma biblioteca de interfaces de áudio para implementar o controle de volume com retorno tátil.

Para mais informações, consulte o Horologist (link em inglês) no GitHub.