Жесты одной рукой в Compose


В Wear OS 7 (уровень API 37) и более поздних версиях фреймворк жестов одной рукой и API, входящий в состав Compose для Wear OS, позволяют пользователям взаимодействовать с вашим приложением без касаний.

Изначально фреймворк поддерживался на устройствах Pixel Watch (Pixel Watch 3 и более поздних версиях), но теперь он доступен всем OEM-производителям. При использовании этого API поддержка жестов в вашем приложении будет автоматически масштабироваться по всей экосистеме по мере расширения поддержки оборудования.

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

Поддерживаемые жесты и действия

Фреймворк жестов Wear OS поддерживает два типа жестов:

  • Основное действие (двойное сведение пальцев). Позволяет выполнить главное действие на экране, например ответить на звонок или включить воспроизведение медиаконтента.
  • Отмена действия (поворот запястья). Сопоставляется с навигацией назад, закрытием диалогового окна или отменой запроса.

Настройка жестов в Compose

Хотя API жестов одной рукой может улучшить ваш пользовательский интерфейс, важно иметь в виду, что некоторые аппаратные средства и OEM-производители не поддерживают эти жесты. Если API обнаружит, что ваше приложение запущено на одном из таких устройств, библиотека автоматически отключит функцию, не влияя на стандартные сенсорные взаимодействия.

Как и в случае со стандартным поведением Compose, вы можете включить жесты одной рукой для элементов пользовательского интерфейса с помощью модификаторов. Вы настраиваете жесты приложения в соответствии с выполняемым действием (основным или отменой) и gestureId для координации с пользовательскими настройками на уровне системы, такими как частота показа подсказок и отключение звука. Для этого нужно создать объект OneHandedGestureConfiguration. Рекомендуем использовать функцию rememberOneHandedGestureConfiguration. В OneHandedGestureConfiguration также можно задать приоритет жеста.

Функция rememberOneHandedGestureConfiguration отслеживает историю взаимодействия пользователя с приложением в разных рекомпозициях, не раскрывая состояние приложения. После создания конфигурации приложение должно передать ее в Modifier.oneHandedGesture интерактивной composable-функции.

Чтобы помочь пользователям узнать о доступных жестах, библиотека предоставляет метод OneHandedGestureClickIndicator. Этот метод действует как оболочка, которая заменяет базовый контент, чтобы показать пользователю, что доступно действие с жестом.

Интерактивные компоненты

Чтобы включить жесты для интерактивного элемента управления, например кнопки, создайте конфигурацию, указав OneHandedGestureAction.Primary, и примените модификатор oneHandedGesture. Передайте один и тот же объект MutableInteractionSource элементу управления и модификатору, чтобы события жестов вызывали визуальный отклик на нажатие.

Чтобы включить индикатор жестов, создайте и запомните экземпляр OneHandedGestureClickIndicatorState. Затем, чтобы активировать визуальную обратную связь, вызовите showIndicator в обратном вызове onGestureAvailable, предоставленном модификатором oneHandedGesture, который сигнализирует системе о том, что произошло событие индикации. После вызова компонент на короткое время заменяет обычный контент анимацией жеста.

var isPlaying by remember { mutableStateOf(false) }
val onClick = { isPlaying = !isPlaying }

val gestureConfig = rememberOneHandedGestureConfiguration(
    action = OneHandedGestureAction.Primary
)
val indicatorState = remember { OneHandedGestureClickIndicatorState() }
val coroutineScope = rememberCoroutineScope()
val interactionSource = remember { MutableInteractionSource() }

Button(
    onClick = onClick,
    interactionSource = interactionSource,
    modifier = Modifier
        .fillMaxWidth()
        .oneHandedGesture(
            gestureConfiguration = gestureConfig,
            interactionSource = interactionSource,
            onGestureLabel = if (isPlaying) "pause" else "play",
            onGestureAvailable = { coroutineScope.launch { indicatorState.showIndicator() } },
            onGesture = onClick
        )
) {
    OneHandedGestureClickIndicator(
        gestureConfiguration = gestureConfig,
        state = indicatorState
    ) {
        Text(if (isPlaying) "Pause" else "Play", modifier = Modifier.fillMaxWidth())
    }
}

Прокручиваемые контейнеры

Для прокручиваемых экранов или списков создайте конфигурацию, указав OneHandedGestureAction.Primary, и примените модификатор oneHandedGesture к контейнеру, вызвав вспомогательную функцию прокрутки, например scrollDown.

Чтобы обеспечить визуальную обратную связь для действий прокрутки, вы можете использовать OneHandedGestureScrollIndicator. Этот компонент работает как стандартный индикатор прокрутки, который показывает положение прокрутки, но он также может указывать на то, что пользователю доступен жест прокрутки. Этот индикатор обычно передается в слот scrollIndicator элемента ScreenScaffold и связан с состоянием прокручиваемого контейнера, например TransformingLazyColumn. Он также наблюдает за OneHandedGestureScrollIndicatorState, чтобы управлять визуальными переходами.

Чтобы активировать визуальную обратную связь, вызовите showIndicator в этом состоянии – обычно внутри обратного вызова onGestureAvailable модификатора oneHandedGesture. После активации индикатор временно заменяет свое стандартное визуальное состояние на последовательность анимации жестов, чтобы предупредить пользователя.

val scrollState = rememberTransformingLazyColumnState()
val gestureConfig = rememberOneHandedGestureConfiguration(
    action = OneHandedGestureAction.Primary,
    priority = OneHandedGesturePriority.Scrollable
)
val indicatorState = remember(gestureConfig) { OneHandedGestureScrollIndicatorState() }
val coroutineScope = rememberCoroutineScope()

ScreenScaffold(
    scrollState = scrollState,
    scrollIndicator = {
        OneHandedGestureScrollIndicator(
            gestureConfiguration = gestureConfig,
            indicatorState = indicatorState,
            scrollState = scrollState,
            modifier = Modifier.align(Alignment.CenterEnd)
        )
    }
) { contentPadding ->
    TransformingLazyColumn(
        state = scrollState,
        contentPadding = contentPadding,
        modifier = Modifier
            .fillMaxSize()
            .oneHandedGesture(
                gestureConfiguration = gestureConfig,
                onGestureLabel = "scroll",
                onGestureAvailable = {
                    coroutineScope.launch { indicatorState.showIndicator() }
                },
                onGesture = { OneHandedGestureDefaults.scrollDown(scrollState) }
            )
    ) {
        items(10) { index ->
            Text("Item $index", modifier = Modifier.padding(8.dp))
        }
    }
}

Как объединить несколько жестов

Вы можете настроить жест прокрутки и жест нажатия с одним и тем же основным действием, добавив gesturePriority в объект OneHandedGestureConfiguration:

  • OneHandedGesturePriority.Clickable (наивысший). Назначайте его интерактивным элементам управления, например типа Button или Card, чтобы они реагировали на жесты, когда видны на экране.
  • OneHandedGesturePriority.Scrollable (средний). Назначайте контейнерам с прокруткой или разбивкой на страницы, чтобы они уступали дочерним элементам, на которые можно нажать, но прокручивались, когда нет видимых элементов управления, на которые можно нажать.
  • OneHandedGesturePriority.Unspecified (самый низкий) – приоритет не назначен. Это значение по умолчанию для жеста, у которого не задано значение priority.

Явно задав priority = OneHandedGesturePriority.Clickable для внутренней кнопки и priority = OneHandedGesturePriority.Scrollable для ее родительского списка, вы можете настроить приоритет жестов. Когда пользователь активирует основное действие жестом одной руки, список сначала прокручивается вниз, пока кнопка не станет видна, а затем фиксируется нажатие кнопки.

Как тестировать и отлаживать жесты с помощью ADB

Вы можете протестировать жесты одной рукой на физическом устройстве или эмуляторе, не выполняя физических движений запястьем, с помощью Android Debug Bridge (adb) и системного сервиса IWearGestureService.

Включить симуляцию жестов

Прежде чем симулировать жесты с помощью ADB, настройте устройство и переопределите ограничения:

  1. Убедитесь, что на устройстве Wear OS установлена Wear OS 7 (уровень API 37) или более поздняя версия.

    adb shell getprop ro.build.version.sdk
    
  2. Если вы проводите тестирование на физическом устройстве, которое не надето на запястье или находится на зарядном устройстве, переопределите ограничение, чтобы фреймворк жестов оставался активным:

    adb shell cmd IWearGestureService override-constraints offbody-state
    

Как активировать события жестов с помощью ADB

Чтобы симулировать жест Двойное сведение пальцев (действие Primary на часах Pixel Watch), выполните следующую команду ADB:

adb shell cmd IWearGestureService gesture DoublePinch

Чтобы симулировать жест Поворот запястья (действие Dismiss на часах Pixel Watch), выполните следующую команду ADB:

adb shell cmd IWearGestureService gesture WristTurn

Сбросить отслеживание подсказок по жестам

Система отслеживает историю взаимодействия пользователя и показывает плавающие подсказки по жестам в соответствии с глобальной настройкой частоты (например, Всегда или Ежедневно). При отладке индикаторов жестов в приложении сбросьте историю отслеживания, чтобы подсказки снова появились для вашего пакета:

  • На сборках или эмуляторах userdebug:

    adb shell cmd IWearGestureService hint clear <your_package_name>
    
  • На розничных сборках (user):

    На коммерческих устройствах без root-доступа hint clear блокируется системными разрешениями. Чтобы сбросить подсказки, очистите локальные данные приложения:

    adb shell pm clear <your_package_name>
    

Восстановить ограничения по умолчанию

Чтобы сбросить все переопределения ограничений отладки после завершения тестирования, выполните следующие действия:

adb shell cmd IWearGestureService override-constraints reset

Устранение неполадок с имитацией жестов

Если ваше приложение не получает имитированные жесты:

  1. Убедитесь, что экран часов включен и не находится в спящем режиме. Фреймворк жестов не отправляет жесты приложениям, когда экран выключен или находится в спящем режиме. Чтобы включить экран с помощью ADB, выполните следующую команду:

    adb shell input keyevent KEYCODE_WAKEUP
    
  2. Проверьте, зарегистрировано ли ваше приложение как активный подписчик на жесты и находится ли оно в фокусе окна:

    adb shell cmd IWearGestureService get-active-gestures -readable
    

    Если экран жестов приложения находится на переднем плане и активен, эта команда возвращает [DoublePinch] или [DoublePinch, WristTurn]. Если возвращается пустой список ([]), проверьте, находится ли окно в фокусе и не блокируют ли активацию ограничения, связанные с тем, что устройство не надето.

  3. Проверьте состояние внутреннего сервиса жестов и активные токены подписчиков:

    adb shell dumpsys IWearGestureService
    

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

Рекомендации по использованию жестов одной рукой можно найти в этой статье.