使用 Compose 進行單手手勢操作


從 Wear OS 7 (API 級別 37) 開始,單手勢架構和 Compose for Wear OS 的 API,可讓使用者與應用程式互動時不必觸控螢幕。

這項架構最初支援 Pixel Watch 裝置 (Pixel Watch 3 和後續機型),但現在所有原始設備製造商都能使用。採用這項 API 後,隨著硬體支援範圍擴大,應用程式的手勢支援功能也會自動在整個生態系統中擴展。

為協助使用者探索可用手勢,同時避免使用者介面過於雜亂,Wear OS 架構提供動畫手勢指標。這些視覺提示會標示可執行手勢的位置,而系統會根據使用者偏好設定,自動管理提示的顯示頻率和靜音頻率。

支援的手勢和動作

Wear OS 手勢架構支援兩種手勢類型:

  • 主要動作 (雙指捏合):對應螢幕上的主要動作,例如接聽電話或切換媒體播放。
  • 取消動作 (轉動手腕):對應向後導覽、關閉對話方塊或取消提示。

在 Compose 中設定手勢

雖然單手操作手勢 API 可以提升 UI,但請務必注意,部分硬體和原始設備製造商不支援這些手勢。如果 API 偵測到應用程式在不支援的裝置上執行,程式庫會自動執行空作業,不會影響標準觸控互動。

與標準 Compose 行為相同,您可以使用修飾符,在 UI 元素上啟用單手手勢。您可以根據要執行的動作 (主要或關閉) 設定應用程式的手勢,並使用 gestureId 配合系統層級的使用者偏好設定,例如提示顯示步頻和頻率靜音。您可以建立 OneHandedGestureConfiguration 物件來表示這項設定,建議使用 rememberOneHandedGestureConfiguration 函式建立該物件。您也可以在 OneHandedGestureConfiguration 中提供手勢優先順序。

rememberOneHandedGestureConfiguration 函式會追蹤重組作業中的使用者互動記錄,但不會公開應用程式狀態。應用程式建立設定後,應將設定傳遞至互動式可組合函式中的 Modifier.oneHandedGesture

為協助使用者探索可用的手勢,程式庫提供 OneHandedGestureClickIndicator 方法。這個方法會做為包裝函式,取代基礎內容,向使用者指出可執行的手勢動作。

互動式元件

如要在按鈕等互動式控制項上啟用手勢,請建立指定 OneHandedGestureAction.Primary 的設定,並套用 oneHandedGesture 修飾符。將相同的 MutableInteractionSource 傳遞至控制項和修飾符,手勢事件就會在控制項上發出視覺按壓回饋。

如要啟用手勢指標,請建立並記錄 OneHandedGestureClickIndicatorState 的例項。接著,在 oneHandedGesture 修飾符提供的 onGestureAvailable 回呼中呼叫 showIndicator,即可觸發視覺回饋,向系統發出發生指標事件的信號。呼叫後,元件會暫時以手勢動畫取代正常內容。

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。這個元件可做為標準捲動指標,顯示捲動位置,但也可以指出使用者可進行捲動手勢。這項指標通常會傳遞至 ScreenScaffoldscrollIndicator slot,並與可捲動容器 (例如 TransformingLazyColumn) 的狀態配對。此外,它也會觀察 OneHandedGestureScrollIndicatorState,以便管理視覺轉換。

如要觸發視覺回饋,請在這個狀態下呼叫 showIndicator,通常是在 oneHandedGesture 修飾符的 onGestureAvailable 回呼內。觸發後,指標會暫時以手勢動畫序列取代標準視覺狀態,提醒使用者。

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

結合多個手勢

如要使用相同的基本動作設定捲動和點擊手勢,請在 OneHandedGestureConfiguration 物件中新增 gesturePriority

明確在內部按鈕上設定 priority = OneHandedGesturePriority.Clickable,並在父項清單上設定 priority = OneHandedGesturePriority.Scrollable,系統即可顯示這項手勢優先行為。使用者以單手手勢觸發主要動作時,系統會先向下捲動清單,直到按鈕顯示為止。然後擷取按鈕的點擊動作。

使用 ADB 測試手勢並進行偵錯

您可以在實體裝置或模擬器上測試單手勢,不必實際移動手腕,只要使用 Android Debug Bridge (adb) 和 IWearGestureService 系統服務即可。

啟用手勢模擬

  1. 確認 Wear OS 裝置搭載 Wear OS 7 (API 級別 37) 以上版本。
  2. 如果是在未戴在手腕上或未接上充電器的實體裝置上測試,請覆寫離身感應器狀態,讓裝置保持啟用狀態:
adb shell cmd sensorservice set-off-body-state 0

使用 ADB 觸發手勢事件

如要模擬「雙指撥動」手勢 (即 Pixel Watch 上的 Primary 動作),請執行下列 ADB Shell 指令:

adb shell cmd IWearGestureService gesture 1

如要模擬「手腕轉動」手勢 (即 Pixel Watch 上的 Dismiss 動作),請執行下列 ADB Shell 指令:

adb shell cmd IWearGestureService gesture 2

重設手勢提示追蹤

系統會追蹤使用者互動記錄,並根據全域節奏設定 (例如「一律」或「每天」) 顯示懸浮手勢提示。偵錯應用程式的手勢指標時,請重設這項追蹤記錄,讓系統再次顯示套件的提示:

adb shell cmd IWearGestureService hint clear <your_package_name>

測試完成後,如要重設離身感應器狀態,請按照下列步驟操作:

adb shell cmd sensorservice reset-off-body-state

其他資源

如需單手手勢的使用時機和場合設計指南,請參閱「單手手勢」。