使用 Compose 進行旋轉輸入

所謂旋轉輸入,是指透過轉動或旋轉手錶零件來輸入內容。平均而言,使用者與手錶互動的時間只有幾秒鐘,因此不妨採用旋轉輸入機制,讓使用者快速完成各項工作,打造更優質的使用者體驗。

旋轉輸入在大多數智慧手錶上有三大來源,包括旋轉側邊按鈕 (RSB)、實體邊框或觸控邊框,後兩者是螢幕周圍的環形觸控區。儘管系統預期的行為可能因輸入類型而不同,仍建議您確保所有重要互動都能透過旋轉輸入完成。

捲動

大多數使用者都希望應用程式能夠支援捲動手勢。當內容在畫面上捲動時,請為使用者提供能對旋轉互動做出回應的視覺回饋。這類回饋可包括頁面指標或用於垂直捲動的位置指標

使用 Compose for Wear OS 實作旋轉捲動功能。以下範例中的應用程式具有 Scaffold 和能垂直捲動的 ScalingLazyColumn。Scaffold 為 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 內含可捲動元件和離散值的修飾符,還提供用於處理焦點的公用程式,也有透過觸覺技術實作音量控制的音訊 UI 程式庫。

詳情請參閱 GitHub 上的 Horologist