Поворотный ввод с помощью Compose

Вращающийся ввод относится к вводу данных с частей ваших часов, которые вращаются или вращаются. В среднем пользователи тратят всего несколько секунд на взаимодействие со своими часами. Вы можете улучшить взаимодействие с пользователем, используя поворотный ввод, позволяющий пользователю быстро выполнять различные задачи.

Три основных источника поворотного ввода на большинстве часов включают вращающуюся боковую кнопку (RSB) и либо физическую лицевую панель, либо сенсорную панель, которая представляет собой круглую сенсорную зону вокруг экрана. Хотя ожидаемое поведение может различаться в зависимости от типа ввода, обязательно поддерживайте поворотный ввод для всех основных взаимодействий.

Прокрутка

Большинство пользователей ожидают, что приложения будут поддерживать жест прокрутки. По мере прокрутки контента на экране дайте пользователям визуальную обратную связь в ответ на поворотные взаимодействия. Визуальная обратная связь может включать индикаторы положения вертикальной прокрутки или индикаторы страниц .

Реализуйте поворотную прокрутку с помощью Compose для Wear OS. В этом примере описывается приложение с каркасом и ScalingLazyColumn , который прокручивается по вертикали. Scaffold предоставляет базовую структуру макета для приложений Wear OS и уже имеет слот для индикатора прокрутки. Чтобы показать ход прокрутки, создайте индикатор положения на основе объекта состояния списка. Прокручиваемые представления, включая ScalingLazyColumn , уже имеют состояние прокрутки для добавления поворотного ввода. Чтобы получать события поворотной прокрутки, выполните следующие действия:

  1. Явно запросите фокус с помощью FocusRequester . Используйте HierarchicalFocusCoordinator для более сложных случаев, таких как несколько объектов ScalingLazyColumns в HorizontalPager .

  2. Добавьте модификатор onRotaryScrollEvent , чтобы перехватывать события, которые генерирует система, когда пользователь поворачивает заводную головку или вращает безель. Каждое событие поворота имеет заданное значение в пикселях и прокручивается по вертикали или по горизонтали. Модификатор также имеет обратный вызов, указывающий, использовано ли событие, и останавливает распространение события его родителям при использовании.

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()
            .fillMaxSize(),
        state = listState
    ) {
        // Content goes here
        // ...
    }
}

Дискретные значения

Используйте поворотные устройства для настройки дискретных значений, например регулировки яркости в настройках или выбора чисел в средстве выбора времени при установке будильника.

Подобно ScalingLazyColumn , пикер, ползунок, степпер и другие составные элементы должны иметь фокус для получения поворотного ввода. В случае наличия на экране нескольких прокручиваемых целей, таких как часы и минуты в средстве выбора времени, создайте FocusRequester для каждой цели и соответствующим образом обработайте изменение фокуса, когда пользователь нажимает на часы или минуты.

var selectedColumn by remember { mutableIntStateOf(0) }

val hoursFocusRequester = remember { FocusRequester() }
val minutesRequester = remember { FocusRequester() }
// ...
Scaffold(modifier = Modifier.fillMaxSize()) {
    Row(
        // ...
        // ...
    ) {
        // ...
        Picker(
            readOnly = selectedColumn != 0,
            modifier = Modifier.size(64.dp, 100.dp)
                .onRotaryScrollEvent {
                    coroutineScope.launch {
                        hourState.scrollBy(it.verticalScrollPixels)
                    }
                    true
                }
                .focusRequester(hoursFocusRequester)
                .focusable(),
            onSelected = { selectedColumn = 0 },
            // ...
            // ...
        )
        // ...
        Picker(
            readOnly = selectedColumn != 1,
            modifier = Modifier.size(64.dp, 100.dp)
                .onRotaryScrollEvent {
                    coroutineScope.launch {
                        minuteState.scrollBy(it.verticalScrollPixels)
                    }
                    true
                }
                .focusRequester(minutesRequester)
                .focusable(),
            onSelected = { selectedColumn = 1 },
            // ...
            // ...
        )
        LaunchedEffect(selectedColumn) {
            listOf(
                hoursFocusRequester,
                minutesRequester
            )[selectedColumn]
                .requestFocus()
        }
    }
}

Пользовательские действия

Вы также можете создавать собственные действия, которые реагируют на поворотный ввод в вашем приложении. Например, используйте поворотный ввод для увеличения и уменьшения масштаба или для управления громкостью в мультимедийном приложении.

Если ваш компонент изначально не поддерживает события прокрутки, такие как регулировка громкости, вы можете обрабатывать события прокрутки самостоятельно.

// VolumeScreen.kt

val focusRequester: FocusRequester = remember { FocusRequester() }

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

Создайте собственное состояние, управляемое в модели представления, и настраиваемый обратный вызов, который будет использоваться для обработки событий поворотной прокрутки.

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

Для простоты в предыдущем примере используются значения пикселей, которые, если их действительно использовать, вероятно, будут слишком чувствительными.

Используйте обратный вызов после получения событий, как показано в следующем фрагменте.

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

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

Дополнительные ресурсы

Рассмотрите возможность использования Horological, проекта Google с открытым исходным кодом, который предоставляет набор библиотек Wear, дополняющих функциональность Compose для Wear OS и других API Wear OS. Часовой специалист обеспечивает реализацию для расширенных вариантов использования и множество деталей, специфичных для устройства.

Например, чувствительность разных вращающихся источников входного сигнала может различаться. Для более плавного перехода между значениями вы можете ограничить скорость или добавить привязку или анимацию для перехода. Это позволяет пользователям чувствовать скорость поворота более естественной. Часолог включает модификаторы для прокручиваемых компонентов и дискретных значений. Он также включает в себя утилиты для управления фокусом и библиотеку аудио пользовательского интерфейса для реализации регулировки громкости с помощью тактильных ощущений.

Дополнительную информацию см. в разделе «Хоролог» на GitHub.

{% дословно %} {% дословно %} {% дословно %} {% дословно %}