Compose 中的旋转输入

旋转输入是指手表的旋转部件的输入。平均而言,用户仅花几秒钟时间与其手表互动。您可以使用旋转输入来让用户快速完成各种任务,从而提升用户体验。

在大多数手表上,旋转输入的三个主要来源包括旋转侧面按钮 (RSB) 和物理屏幕边框或轻触屏幕边框(屏幕周围的圆形轻触区域)。虽然预期行为可能会因输入类型而异,但务必要针对所有重要互动支持旋转输入。

滚动

大多数用户都希望应用支持滚动手势。当内容在屏幕上滚动时,为用户提供视觉反馈,以响应旋转互动。视觉反馈可以包含垂直滚动的位置指示器页面指示器

使用 Compose for Wear OS 实现旋转滚动。该示例介绍了具有基架且会垂直滚动的 ScalingLazyColumn 的应用。基架可为 Wear OS 应用提供基本布局结构,并且已有一个用于滚动指示器的槽位。如需显示滚动进度,请根据列表状态对象创建位置指示器。可滚动视图(包括 ScalingLazyColumn)已具有用于添加旋转输入的可滚动状态。如需接收旋转滚动事件,请执行以下操作:

  1. 使用 FocusRequester 明确请求焦点。
  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(),
        state = listState
    ) { ... }
}

离散值

您还可以通过旋转互动来调整离散值,例如在设置中调整亮度或在设置闹钟时在时间选择器中选择数字。

ScalingLazyColumn 类似,选择器、滑块、步进器和其他可组合项需要获得焦点才能接收旋转输入。如果屏幕上有多个可滚动的目标,例如时间选择器中的小时和分钟,请为每个目标创建一个 FocusRequester,并在用户点按小时或分钟时相应地处理焦点更改。

@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()
    }
}

自定义操作

您还可以创建自定义操作,以响应应用中的旋转输入。例如,使用旋转输入进行缩放,或控制媒体应用中的音量。

如果您的组件本身不支持滚动事件(例如音量控制),您可以自行处理滚动事件。

// 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(),
) { ... }

其他资源

建议使用 Horologist,这是一个 Google 开源项目,提供一组 Wear 库,对 Compose for Wear OS 和其他 Wear OS API 的功能进行了补充。Horologist 提供了高级用例的实现,以及许多特定于设备的细节。

例如,不同旋转输入源的灵敏度可能有所不同。若要在值之间实现更顺畅的过渡,您可以对过渡进行速率限制或添加贴靠或动画。这样能让用户感觉旋转速度更自然。Horologist 包含可滚动组件和离散值的修饰符。它还包括用于处理焦点的实用程序和用于通过触感反馈实现音量控制的音频界面库。

如需了解详情,请参阅 GitHub 上的 Horologist