Entrada rotativa con Compose

La entrada rotativa hace referencia a la entrada de piezas de tu reloj que giran o rotan. En promedio, los usuarios solo interactúan unos segundos con su reloj. Puedes mejorar la experiencia del usuario con la entrada rotativa para que este pueda realizar varias tareas rápidamente.

Las tres fuentes principales de entrada rotativa en la mayoría de los relojes incluyen el botón lateral giratorio (RSB) y un bisel físico o un bisel táctil, que es una zona táctil circular alrededor de la pantalla. Aunque el comportamiento esperado puede variar según el tipo de entrada, asegúrate de admitir la entrada rotativa para todas las interacciones esenciales.

Desplazamiento

La mayoría de los usuarios esperan que las apps admitan el gesto de desplazamiento. A medida que el contenido se desplaza en la pantalla, brinda a los usuarios respuestas visuales a las interacciones rotativas. Las respuestas visuales pueden incluir indicadores de posición para el desplazamiento vertical o indicadores de página.

Implementa el desplazamiento rotativo con Compose para Wear OS. En este ejemplo, se describe una app con un andamiaje y una ScalingLazyColumn que se desplaza verticalmente. El andamiaje proporciona la estructura de diseño básica de apps para Wear OS y ya tiene una ranura para un indicador de desplazamiento. Para mostrar el progreso del desplazamiento, crea un indicador de posición basado en el objeto de estado de la lista. Las vistas desplazables, incluida ScalingLazyColumn, ya tienen un estado desplazable para agregar entradas rotativas. Para recibir eventos de desplazamiento rotativos, haz lo siguiente:

  1. Solicita de forma explícita el enfoque usando FocusRequester.
  2. Agrega el modificador onRotaryScrollEvent para interceptar eventos que genera el sistema cuando un usuario gira la corona o rota el bisel. Cada evento rotativo tiene un valor de píxeles establecido y se desplaza horizontal o verticalmente. El modificador también tiene una devolución de llamada para indicar si se consume el evento y detiene su propagación a sus elementos superiores cuando se consume.
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

Usa interacciones rotativas para ajustar también valores discretos, como regular el brillo en la configuración o seleccionar los números en el selector de hora cuando configures una alarma.

De manera similar a lo que ocurre con ScalingLazyColumn, el selector, el control deslizante, el objeto Stepper y otros elementos componibles deben tener enfoque para recibir la entrada rotativa. Si hay varios objetivos desplazables en la pantalla, como las horas y los minutos del selector de hora, crea un FocusRequester para cada objetivo y controla el cambio de enfoque según corresponda cuando el usuario presione las horas o los 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()
    }
}

Acciones personalizadas

También puedes crear acciones personalizadas que respondan a entradas rotativas en tu app. Por ejemplo, usa la entrada rotativa para acercar y alejar, o para controlar el volumen en una app de contenido multimedia.

Si tu componente no admite eventos de desplazamiento de forma nativa, como el control de volumen, puedes controlarlos por tu cuenta.

// VolumeScreen.kt

val focusRequester: FocusRequester = remember { FocusRequester() }

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

Crea un estado personalizado administrado en un modelo de vistas y una devolución de llamada personalizada que se use para procesar eventos de desplazamiento rotativos.

// 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)
    }
}

Por razones de simplicidad, en el ejemplo anterior se utilizan valores de píxeles que, si se usan, podrían ser demasiado sensibles.

Usa la devolución de llamada una vez que recibas los eventos, como se muestra en el siguiente fragmento.

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

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

Recursos adicionales

Considera usar Horologist, un proyecto de código abierto de Google que proporciona un conjunto de bibliotecas de Wear que complementan la funcionalidad que proporciona Compose para Wear OS y otras APIs de Wear OS. Horologist proporciona la implementación para casos de uso avanzado y muchos detalles específicos del dispositivo.

Por ejemplo, la sensibilidad de diferentes fuentes de entrada rotativa puede variar. Para obtener una transición más fluida entre valores, puedes establecer un límite de frecuencia o agregar ajustes o animaciones para la transición. Esto permite que la velocidad de giro se sienta más natural para los usuarios. Horologist incluye modificadores para componentes desplazables y para valores discretos. También incluye utilidades para controlar el enfoque y una biblioteca de IU de audio para implementar el control de volumen con tecnología táctil.

Para obtener más información, consulta Horologist en GitHub.