Compose를 사용한 로터리 입력

로터리 입력은 시계에서 회전하는 부분의 입력을 의미합니다. 평균적으로 사용자가 시계와 상호작용하는 데는 몇 초밖에 걸리지 않습니다. 로터리 입력을 사용하면 사용자가 다양한 작업을 빠르게 완료할 수 있어 사용자 환경을 개선할 수 있습니다.

대부분의 시계에서 로터리 입력의 세 가지 기본 소스에는 회전 측면 버튼(RSB)과 물리적 베젤 또는 터치 베젤(화면 주변의 원형 터치 영역)이 있습니다. 예상되는 동작은 입력 유형에 따라 다를 수 있지만 모든 필수 상호작용에는 로터리 입력을 지원해야 합니다.

스크롤

대부분의 사용자는 앱이 스크롤 동작을 지원할 것으로 기대합니다. 콘텐츠가 화면에서 스크롤될 때 로터리 상호작용에 대한 응답으로 시각적 피드백을 사용자에게 제공합니다. 시각적 피드백에는 세로 스크롤을 위한 위치 표시기 또는 페이지 표시기가 포함될 수 있습니다.

Wear OS용 Compose를 사용하여 로터리 스크롤을 구현합니다. 이 예에서는 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(),
) { ... }

추가 리소스

Google 오픈소스 프로젝트인 Horologist를 사용해 보세요. Wear OS용 Compose 및 기타 Wear OS API에서 제공하는 기능을 보완하는 Wear 라이브러리 세트를 제공합니다. Horologist는 고급 사용 사례 구현과 여러 기기별 세부정보를 제공합니다.

예를 들어 다양한 로터리 입력 소스의 민감도는 다를 수 있습니다. 값 간의 전환을 더 원활하게 하려면 비율 제한을 적용하거나 전환을 위한 맞추기 또는 애니메이션을 추가할 수 있습니다. 이렇게 하면 사용자에게 회전 속도가 더 자연스럽게 느껴질 수 있습니다. Horologist에는 스크롤 가능한 구성요소 및 이산값의 수정자가 포함되어 있습니다. 포커스를 처리하는 유틸리티와 햅틱으로 볼륨 조절을 구현하는 오디오 UI 라이브러리도 포함되어 있습니다.

자세한 내용은 GitHub의 Horologist를 참고하세요.