Ввод вращением с помощью Compose


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

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

Индикатор прокрутки

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

ScalingLazyColumn, TransformingLazyColumn и Picker по умолчанию поддерживают жест прокрутки, если вы разместите эти компоненты внутри AppScaffold и ScreenScaffold и передадите состояние списка между ScreenScaffold и компонентом, например TransformingLazyColumn.

AppScaffold и ScreenScaffold предоставляют базовую структуру макета для приложений Wear OS и уже имеют слот для индикатора прокрутки с реализацией по умолчанию. Чтобы настроить индикатор прокрутки, создайте его на основе объекта состояния списка, как показано в следующем фрагменте кода:

val listState = rememberTransformingLazyColumnState()
ScreenScaffold(
    scrollState = listState,
    scrollIndicator = {
        ScrollIndicator(state = listState)
    }
) {
    // ...
}

Чтобы настроить поведение привязки для ScalingLazyColumn, используйте ScalingLazyColumnDefaults.snapFlingBehavior, как показано в следующем фрагменте кода:

val listState = rememberScalingLazyListState()
ScreenScaffold(
    scrollState = listState,
    scrollIndicator = {
        ScrollIndicator(state = listState)
    }
) {

    val state = rememberScalingLazyListState()
    ScalingLazyColumn(
        modifier = Modifier.fillMaxWidth(),
        state = state,
        flingBehavior = ScalingLazyColumnDefaults.snapFlingBehavior(state = state)
    ) {
        // Content goes here
        // ...
    }
}

Специальные действия

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

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

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

class VolumeRange(
    val max: Int = 10,
    val min: Int = 0
)

private object VolumeViewModel {
    class MyViewModel : ViewModel() {
        private val _volumeState = mutableIntStateOf(0)
        val volumeState: State<Int>
            get() = _volumeState

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

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

val focusRequester: FocusRequester = remember { FocusRequester() }
val volumeViewModel: VolumeViewModel.MyViewModel =
    viewModel()
val volumeState by volumeViewModel.volumeState

TransformingLazyColumn(
    modifier = Modifier
        .fillMaxSize()
        .onRotaryScrollEvent {
            volumeViewModel.onVolumeChangeByScroll(it.verticalScrollPixels)
            true
        }
        .focusRequester(focusRequester)
        .focusable(),
) {
    // You can use volumeState here, for example:
    item {
        Text("Volume: $volumeState")
    }
}

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