Wear OS용 Compose Codelab

1. 소개

11ba5a682f1ffca3.png

Wear OS용 Compose를 사용하면 Jetpack Compose로 앱을 빌드하는 것과 관련해 학습한 지식을 웨어러블 기기에 적용할 수 있습니다.

Material You 지원이 내장된 Wear OS용 Compose는 UI 개발을 간소화 및 가속화하며 코딩 작업을 줄이고도 훌륭한 앱을 개발할 수 있게 해 줍니다.

이 Codelab에서는 Compose에 관해 전문가 수준은 아니지만 어느 정도의 지식이 필요합니다.

몇 가지 Wear 전용 컴포저블(단순한 것과 복잡한 것 모두)을 만들게 됩니다. 과정을 마치면 나만의 Wear OS용 앱을 작성할 수 있습니다. 그럼 시작해 볼까요?

학습할 내용

  • 이전 Compose 환경 간의 유사점과 차이점
  • 간단한 컴포저블 및 컴포저블이 Wear OS에서 작동하는 방식
  • Wear OS 전용 컴포저블
  • Wear OS의 LazyColumn(ScalingLazyColumn)
  • Wear OS의 Scaffold 버전

빌드할 항목

Wear OS에 최적화된 컴포저블에 대한 스크롤 가능한 목록을 표시하는 간단한 앱을 빌드하게 됩니다.

Scaffold를 사용하기 때문에, 곡선 텍스트 시간(상단에 표시됨)과 비네트, 마지막으로 스크롤 표시기(기기 측면에 표시됨)도 갖게 됩니다.

이 Codelab을 마치면 다음과 같이 표시됩니다.

31cb08c0fa035400.gif

기본 요건

2. 설정하기

이 단계에서는 환경을 설정하고 시작 프로젝트를 다운로드해 보겠습니다.

준비 사항

  • Android 스튜디오의 최신 안정화 버전
  • Wear OS 기기 또는 에뮬레이터(에뮬레이터를 처음 사용하시나요? 설정하는 방법을 여기에서 알아보세요.)

코드 다운로드

git이 설치되어 있으면 아래 명령어를 실행하여 이 저장소의 코드를 클론하면 됩니다. git이 설치되어 있는지 확인하려면 터미널이나 명령줄에 git --version을 입력하여 올바르게 실행되는지 확인합니다.

git clone https://github.com/android/codelab-compose-for-wear-os.git
cd compose-for-wear-os

git이 없는 경우 다음 버튼을 클릭하여 이 Codelab을 위한 모든 코드를 다운로드할 수 있습니다.

언제든지 툴바에서 실행 구성을 변경하여 Android 스튜디오에서 어느 한 모듈을 실행할 수 있습니다.

400c194c8948c952.png

Android 스튜디오에서 프로젝트 열기

  1. 'Welcome to Android Studio' 창에서 61d0a4432ef6d396.png Open an Existing Project를 선택합니다.
  2. [Download Location] 폴더를 선택합니다.
  3. Android 스튜디오에서 프로젝트를 가져오면 Wear OS 에뮬레이터 또는 실제 기기에서 startfinished 모듈을 실행할 수 있는지 테스트합니다.
  4. start 모듈은 아래 스크린샷과 같이 표시됩니다. 이 모듈에서 모든 작업을 진행합니다.

c82b07a089099c4f.png

시작 코드 살펴보기

  • build.gradle에는 기본 앱 구성이 포함되어 있습니다. Composable Wear OS 앱을 만드는 데 필요한 종속 항목도 포함되어 있습니다. Jetpack Compose와 Wear OS 버전 간의 유사점과 차이점에 관해 알아보겠습니다.
  • main > AndroidManifest.xml에는 Wear OS 애플리케이션을 만드는 데 필요한 요소가 포함되어 있습니다. 이 앱은 비 Compose 앱과는 동일하고 모바일 앱과도 비슷하기 때문에 따로 살펴보지 않겠습니다.
  • main > theme/ 폴더에는 Compose에서 테마용으로 사용되는 Color, TypeTheme 파일이 포함되어 있습니다.
  • main > MainActivity.kt에는 Compose로 앱을 만들 때 사용하는 상용구가 포함되어 있습니다. 또한 앱의 최상위 컴포저블(예: ScaffoldScalingLazyList)도 포함되어 있습니다.
  • main > ReusableComponents.kt에는 여기서 만들 대부분의 Wear 전용 컴포저블을 위한 함수가 포함되어 있습니다. 이 파일에서 상당수의 작업을 처리하게 됩니다.

3. 종속 항목 검토

Wear와 관련된 종속 항목 변경은 대부분 최상위 아키텍처 레이어에서 이루어집니다(아래에 빨간색으로 강조 표시됨).

d64d9c262a79271.png

즉, Jetpack Compose와 함께 이미 사용하는 많은 종속 항목은 Wear OS를 타겟팅할 때 변경되지 않습니다. 예를 들어 UI, Runtime, Compiler, Animation 종속 항목은 동일하게 유지됩니다.

그러나 이전에 사용한 라이브러리와 다른 적절한 Wear OS Material, FoundationNavigation 라이브러리를 사용해야 합니다.

아래 비교 내용을 참고하여 차이점을 확인할 수 있습니다.

Wear OS 종속 항목(androidx.wear.*)

비교

표준 종속 항목(androidx.*)

androidx.wear.compose:compose-material

오른쪽 대신

androidx.compose.material:material

androidx.wear.compose:compose-navigation

오른쪽 대신

androidx.navigation:navigation-compose

androidx.wear.compose:compose-foundation

오른쪽 항목에 추가

androidx.compose.foundation:foundation

androidx.wear.compose:compose-ui-tooling

오른쪽 항목에 추가

androidx.compose.ui:ui-tooling-preview

1. 개발자는 Wear Compose Material 라이브러리로 확장된 머티리얼 리플, 머티리얼 아이콘 등 기타 머티리얼 관련 라이브러리를 계속 사용할 수 있습니다.

build.gradle을 열고 start 모듈에서 'TODO: Review Dependencies'를 검색합니다. 이 단계는 종속 항목을 검토하기 위한 것으로, 코드를 추가하지는 않습니다.

start/build.gradle:

// TODO: Review Dependencies
// General Compose dependencies
implementation "androidx.activity:activity-compose:$activity_compose_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
implementation "androidx.compose.foundation:foundation:$compose_version"
implementation "androidx.compose.material:material-icons-extended:$compose_version"

// Compose for Wear OS Dependencies
implementation "androidx.wear.compose:compose-material:$wear_compose_version"

// Foundation is additive, so you can use the standard version in your Wear OS app.
implementation "androidx.wear.compose:compose-foundation:$wear_compose_version"

// Compose preview annotations for Wear OS.
implementation "androidx.wear.compose:compose-ui-tooling:$wear_compose_version"

일반 Compose 종속 항목 대부분은 이미 알고 계시므로 여기서는 따로 다루지 않겠습니다.

Wear OS 종속 항목으로 넘어가겠습니다.

앞서 간략히 언급한 것처럼 material의 Wear OS 전용 버전(androidx.wear.compose:compose-material)만 포함되어 있습니다. 즉, 프로젝트에 androidx.compose.material:material이 표시되거나 포함되지 않습니다.

개발자가 Wear Material과 함께 다른 머티리얼 라이브러리를 사용할 수 있다는 점을 알려야 합니다. 이를 위해 실제로 이 Codelab에서는 androidx.compose.material:material-icons-extended를 포함합니다.

마지막으로, Compose용 Wear foundation 라이브러리(androidx.wear.compose:compose-foundation)를 포함합니다. 이는 추가되는 항목이기 때문에 이전에 사용한 표준 foundation과 함께 사용할 수 있습니다. 이 항목이 일반 Compose 종속 항목에 포함되었다는 것을 이미 알고 계실 수도 있습니다.

이제 종속 항목을 이해했으므로 기본 앱을 살펴보겠습니다.

4. MainActivity 검토

모든 작업을

start

모듈에서 진행하므로, 모든 파일이 이 모듈에 열려 있는지 확인하세요.

먼저 start 모듈에서 MainActivity를 열어 시작합니다.

이는 ComponentActivity를 확장하고 setContent { WearApp() }을 사용하여 UI를 만드는 상당히 간단한 클래스입니다.

Compose에 관한 사전 지식이 있다면 이 클래스가 친숙할 것입니다. 단순히 UI를 설정하기만 합니다.

WearApp() 구성 가능한 함수까지 아래로 스크롤합니다. 코드 자체에 관해 설명하기 전에, 코드 전반에 걸쳐 다수의 TODO가 산재해 있음을 알 수 있습니다. 각각은 이 Codelab의 단계를 나타냅니다. 지금은 이를 무시해도 됩니다.

예를 들면 다음과 같습니다.

재미있는 WearApp()의 코드:

WearAppTheme {
    // TODO: Swap to ScalingLazyListState
    val listState = rememberLazyListState()

    /* *************************** Part 4: Wear OS Scaffold *************************** */
    // TODO (Start): Create a Scaffold (Wear Version)

        // Modifiers used by our Wear composables.
        val contentModifier = Modifier.fillMaxWidth().padding(bottom = 8.dp)
        val iconModifier = Modifier.size(24.dp).wrapContentSize(align = Alignment.Center)

        /* *************************** Part 3: ScalingLazyColumn *************************** */
        // TODO: Create a ScalingLazyColumn (Wear's version of LazyColumn)
        LazyColumn(
            modifier = Modifier.fillMaxSize(),
            contentPadding = PaddingValues(
                top = 32.dp,
                start = 8.dp,
                end = 8.dp,
                bottom = 32.dp
            ),
            verticalArrangement = Arrangement.Center,
            state = listState
        ) {

            // TODO: Remove item; for beginning only.
            item { StartOnlyTextComposables() }

            /* ******************* Part 1: Simple composables ******************* */
            item { ButtonExample(contentModifier, iconModifier) }
            item { TextExample(contentModifier) }
            item { CardExample(contentModifier, iconModifier) }

            /* ********************* Part 2: Wear unique composables ********************* */
            item { ChipExample(contentModifier, iconModifier) }
            item { ToggleChipExample(contentModifier) }
        }

    // TODO (End): Create a Scaffold (Wear Version)

}

먼저 테마인 WearAppTheme { }을 설정합니다. 이는 이전에 테마를 작성한 방식과 동일합니다. 즉, 색상, 서체, 모양과 함께 MaterialTheme을 설정합니다.

하지만 Wear OS의 경우 일반적으로 원형과 비원형 기기에 최적화된 기본 Material Wear 모양을 사용하는 것이 좋습니다. 그런 이유로, theme/Theme.kt를 자세히 살펴보면 모양이 재정의되지 않음을 알 수 있습니다.

원할 경우 theme/Theme.kt를 열어 자세히 살펴볼 수 있습니다. 하지만 다시 한번 언급하자면 이전과 동일합니다.

다음으로, 확장할 Wear 컴포저블을 매번 지정할 필요가 없도록 이러한 컴포저블을 위한 몇 가지 수정자를 생성합니다. 주로 콘텐츠를 중앙에 배치하고 패딩을 추가합니다.

그런 다음 LazyColumn을 만듭니다. 이는 이전에 한 것과 마찬가지로 다수 항목의 세로 스크롤 목록을 생성하는 데 사용됩니다.

코드:

item { StartOnlyTextComposables() }

/* ******************* Part 1: Simple composables ******************* */
item { ButtonExample(contentModifier, iconModifier) }
item { TextExample(contentModifier) }
item { CardExample(contentModifier, iconModifier) }

/* ********************* Part 2: Wear unique composables ********************* */
item { ChipExample(contentModifier, iconModifier) }
item { ToggleChipExample(contentModifier) }

항목 자체와 관련해서는 StartOnlyTextComposables()는 UI만 생성합니다. 나머지는 Codelab을 진행하며 채우게 됩니다.

이러한 함수는 사실 다음 섹션에서 찾게 될 ReusableComponents.kt 파일에 있습니다.

Wear OS용 Compose를 시작해 보겠습니다.

5. 간단한 컴포저블 추가

아마도 이미 잘 알고 계실 세 가지 컴포저블(Button, Text, Card)부터 시작해 보겠습니다.

먼저 Hello World 컴포저블을 삭제하겠습니다.

'TODO: Remove item'를 검색하고 주석과 주석 아래의 행을 모두 삭제합니다.

1단계

// TODO: Remove item; for beginning only.
item { StartOnlyTextComposables() }

다음으로, 첫 번째 컴포저블을 추가해 보겠습니다.

Button 컴포저블 만들기

start 모듈에서 ReusableComponents.kt를 열고 'TODO: Create a Button Composable'을 검색한 다음 현재 구성 가능한 메서드를 다음 코드로 바꿉니다.

2단계

// TODO: Create a Button Composable (with a Row to center)
@Composable
fun ButtonExample(
    modifier: Modifier = Modifier,
    iconModifier: Modifier = Modifier
) {
    Row(
        modifier = modifier,
        horizontalArrangement = Arrangement.Center
    ) {
        // Button
        Button(
            modifier = Modifier.size(ButtonDefaults.LargeButtonSize),
            onClick = { /* ... */ },
        ) {
            Icon(
                imageVector = Icons.Rounded.Phone,
                contentDescription = "triggers phone action",
                modifier = iconModifier
            )
        }
    }
}

ButtonExample() 구성 가능한 함수(이 코드가 있는 위치)가 이제 가운데 버튼을 생성합니다.

코드를 살펴보겠습니다.

여기서 RowButton 컴포저블을 원형 화면 중앙에 배치하는 데만 사용됩니다. 이 작업은 MainActivity에서 만든 수정자를 적용하고 이를 이 함수에 전달하여 확인할 수 있습니다. 나중에 원형 화면에서 스크롤할 때 콘텐츠가 잘리지 않도록(화면 중앙에 배치하는 이유임) 할 수 있습니다.

다음으로, Button을 직접 만듭니다. 코드는 이전에 Button에 사용한 것과 동일하지만 여기서는 ButtonDefault.LargeButtonSize를 사용합니다. Wear OS 기기에 최적화된 미리 설정된 크기이므로 반드시 사용해야 합니다.

그다음, 클릭 이벤트를 빈 람다에 설정합니다. 여기서 이러한 컴포저블은 데모용이므로 나중에는 필요하지 않습니다. 하지만 실제 앱에서는 비즈니스 로직을 실행하기 위해 ViewModel 등과 통신하게 됩니다.

그런 다음 버튼 내부에 Icon을 설정합니다. 코드는 이전에 Icon과 관련해 본 것과 동일합니다. androidx.compose.material:material-icons-extended 라이브러리에서도 아이콘을 가져옵니다.

마지막으로 이전에 설정한 수정자를 Icon에 설정합니다.

앱을 실행하면 다음과 같이 표시됩니다.

c9b981101ae653db.png

이 코드는 이전에 이미 작성해 놓았을 것입니다(매우 좋죠). 차이점은 이제 Wear OS에 최적화된 버튼이 생성되었다는 점입니다.

매우 간단합니다. 다른 것을 하나 더 살펴보겠습니다.

Text 컴포저블 만들기

ReusableComponents.kt에서 'TODO: Create a Text Composable'을 검색하고 현재 구성 가능한 메서드를 다음 코드로 바꿉니다.

3단계

// TODO: Create a Text Composable
@Composable
fun TextExample(modifier: Modifier = Modifier) {
    Text(
        modifier = modifier,
        textAlign = TextAlign.Center,
        color = MaterialTheme.colors.primary,
        text = stringResource(R.string.device_shape)
    )
}

Text 컴포저블을 만들고 수정자를 설정하고 텍스트를 정렬한 다음, 색상을 설정하고 마지막으로 문자열 리소스에서 텍스트 자체를 설정합니다.

Text 컴포저블은 Compose 개발자에게 매우 익숙할 것이며, 실제로 코드가 이전에 사용한 코드와 동일합니다.

그 모습을 직접 살펴보겠습니다.

b33172e992d1ea3e.png

이제 TextExample() 구성 가능한 함수(코드를 배치한 위치)는 기본 머티리얼 색상으로 Text 컴포저블을 생성합니다.

문자열은 res/values/strings.xml 파일에서 가져오게 됩니다. 실제로 res/values 폴더를 보면 두 개의 strings.xml 리소스 파일이 있습니다.

Wear OS는 원형 및 비원형 기기용 문자열 리소스를 제공합니다. 따라서 정사각형 에뮬레이터에서 이를 실행하면 문자열이 다음과 같이 변경됩니다.

2e7b20dbfbd23350.png

지금까지는 꽤 순조로웠습니다. 유사한 컴포저블인 Card를 마지막으로 살펴보겠습니다.

Card 컴포저블 만들기

ReusableComponents.kt에서 'TODO: Create a Card'를 검색하고 현재 구성 가능한 메서드를 다음 코드로 바꿉니다.

4단계

// TODO: Create a Card (specifically, an AppCard) Composable
@Composable
fun CardExample(
    modifier: Modifier = Modifier,
    iconModifier: Modifier = Modifier
) {
    AppCard(
        modifier = modifier,
        appImage = {
            Icon(
                imageVector = Icons.Rounded.Message,
                contentDescription = "triggers open message action",
                modifier = iconModifier
            )
        },
        appName = { Text("Messages") },
        time = { Text("12m") },
        title = { Text("Kim Green") },
        onClick = { /* ... */ }
    ) {
        Text("On my way!")
    }
}

Wear는 AppCardTitleCard라는 두 개의 기본 카드가 있다는 점에서 조금 다릅니다.

여기서는 카드에 Icon이 필요하므로 AppCard를 사용하겠습니다. TitleCard의 슬롯이 더 적습니다. 자세한 내용은 카드 가이드를 참고하세요.

AppCard 컴포저블을 만들고 수정자를 설정하고 Icon을 추가한 다음, 몇 가지 Text 컴포저블 매개변수(카드의 영역마다 하나씩)를 추가하고 마지막으로 맨 끝에 기본 콘텐츠 텍스트를 설정합니다.

그 모습을 직접 살펴보겠습니다.

1fc761252ac5b466.png

이 지점에서 이러한 컴포저블의 Compose 코드가 사실상 이전에 사용한 것과 동일하다는 것을 알 수 있습니다. 멋지지 않나요? 이미 배운 모든 지식을 그대로 다시 활용할 수 있습니다.

이제 새로운 몇 가지 컴포저블을 살펴보겠습니다.

6. Wear에 고유한 컴포저블 추가

이 섹션에서는 ChipToggleChip 컴포저블을 살펴봅니다.

Chip 컴포저블 만들기

Chip은 머티리얼 가이드라인에 실제로 명시되어 있습니다. 하지만 표준 머티리얼 라이브러리에는 실제 구성 가능한 함수가 없습니다.

Chip은 빠른 원탭 동작을 위해 만들어진 것으로, 화면 공간이 제한된 Wear 기기에 특히 적합합니다.

다음은 Chip 구성 가능한 함수의 두어 가지 변형입니다. 이를 통해 여러분이 만들 수 있는 항목에 관한 아이디어를 얻을 수 있습니다.

코드를 작성해 보겠습니다.

ReusableComponents.kt에서 'TODO: Create a Chip'을 검색하고 현재 구성 가능한 메서드를 다음 코드로 바꿉니다.

5단계

// TODO: Create a Chip Composable
@Composable
fun ChipExample(
    modifier: Modifier = Modifier,
    iconModifier: Modifier = Modifier
) {
    Chip(
        modifier = modifier,
        onClick = { /* ... */ },
        label = {
            Text(
                text = "5 minute Meditation",
                maxLines = 1,
                overflow = TextOverflow.Ellipsis
            )
        },
        icon = {
            Icon(
                imageVector = Icons.Rounded.SelfImprovement,
                contentDescription = "triggers meditation action",
                modifier = iconModifier
            )
        },
    )
}

Chip 컴포저블은 다른 컴포저블(예: 수정자 및 onClick)에 사용되는 매개변수 중 상당수를 그대로 사용하므로 매개변수에 관해서는 살펴볼 필요가 없습니다.

이 컴포저블도 라벨(생성하는 Text 컴포저블이 사용되는 대상)과 아이콘을 사용합니다.

Icon 코드는 다른 컴포저블에서 본 코드와 정확히 동일해야 하지만, 이 경우에는 androidx.compose.material:material-icons-extended 라이브러리에서 Self Improvement 아이콘을 가져옵니다.

그 모습을 직접 살펴보겠습니다(아래로 스크롤해야 함).

d97151e85e9a1e03.png

이제 Toggle의 변형인 ToggleChip 컴포저블을 살펴보겠습니다.

ToggleChip 컴포저블 만들기

ToggleChipChip과 유사하지만 사용자가 라디오 버튼, 전환 버튼 또는 체크박스와 상호작용할 수 있도록 해 줍니다.

ReusableComponents.kt에서 'TODO: Create a ToggleChip'을 검색하고 현재 구성 가능한 메서드를 다음 코드로 바꿉니다.

6단계

// TODO: Create a ToggleChip Composable
@Composable
fun ToggleChipExample(modifier: Modifier = Modifier) {
    var checked by remember { mutableStateOf(true) }
    ToggleChip(
        modifier = modifier,
        checked = checked,
        toggleControl = {
            Switch(
                checked = checked,
                modifier = Modifier.semantics {
                    this.contentDescription = if (checked) "On" else "Off"
                }
            )
        },
        onCheckedChange = {
            checked = it
        },
        label = {
            Text(
                text = "Sound",
                maxLines = 1,
                overflow = TextOverflow.Ellipsis
            )
        }
    )
}

이제 ToggleChipExample() 구성 가능한 함수(이 코드가 있는 위치)가 체크박스나 라디오 버튼이 아닌 전환 버튼을 사용하여 ToggleChip을 생성합니다.

먼저 MutableState를 만듭니다. 다른 함수에서는 이 작업을 하지 않았습니다. 여기서는 Wear의 기능을 확인하기 위해 주로 UI 데모를 실행하기 때문입니다.

일반 앱에서는 아마도 컴포저블을 스테이트리스(Stateless)로 만들기 위해, 앱 처리를 위해 선택한 상태와 람다를 전달할 수 있을 것입니다(자세한 내용은 여기 참고).

여기서는 작동하는 전환 버튼과 함께 ToggleChip이 어떻게 동작하는지 보여주기 위해 컴포저블을 단순한 형태로 유지합니다.

다음으로, 원하는 스위치를 구현하기 위해 수정자와 선택한 상태, 전환 컨트롤을 설정합니다.

그런 다음 상태 변경을 위한 람다를 만들고 마지막으로 Text 컴포저블과 일부 기본 매개변수를 갖는 라벨을 설정합니다.

그 모습을 직접 살펴보겠습니다.

ea1a76abd54877b.png

지금까지 많은 Wear OS 전용 컴포저블을 살펴보았습니다. 앞서 언급한 것처럼 대부분의 코드는 이전에 작성한 것과 거의 동일합니다.

이제 좀 더 고급 내용을 살펴보겠습니다.

7. ScalingLazyColumn으로 이전

아마도 모바일 앱에서는 LazyColumn을 사용해 세로로 스크롤되는 목록을 만들었을 것입니다.

원형 기기는 상단과 하단이 더 좁기 때문에 항목을 표시할 공간이 적습니다. 그런 이유로 이러한 둥근 기기에 관한 지원을 높이기 위해 Wear OS에는 자체 버전의 LazyColumn이 있습니다.

ScalingLazyColumnLazyColumn을 확장하여 화면 상단과 하단에서의 크기 조정과 투명도를 모두 지원합니다. 이를 통해 사용자는 콘텐츠를 더 쉽게 읽을 수 있습니다.

다음은 데모입니다.

198ee8e8fa799f08.gif

항목이 중심에 가까울수록 전체 크기로 확장된 다음, 중심에서 멀어지면 (더 투명해지면서) 크기가 다시 줄어드는 모습을 볼 수 있습니다.

다음은 앱에 실제로 표시되는 보다 구체적인 예입니다.

a5a83ab2e5d5230f.gif

이렇게 하면 실제로 가독성에 큰 도움이 되는 것을 알 수 있습니다.

ScalingLazyColumn의 동작을 확인했으므로 이제 LazyColumn을 변환해 보겠습니다.

ScalingLazyListState로 변환

MainActivity.kt에서 'TODO: Swap to ScalingLazyListState'를 검색하고 관련 주석과 그 아래에 있는 행을 다음 코드로 대체합니다.

7단계

// TODO: Swap to ScalingLazyListState
val listState = rememberScalingLazyListState()

이름은 'Scaling' 부분을 제외하고 거의 동일합니다. LazyListStateLazyColumn의 상태를 처리하는 것처럼 ScalingLazyListStateScalingLazyColumn의 상태를 처리합니다.

ScalingLazyColumn으로 변환

그다음, ScalingLazyColumn으로 바꿉니다.

MainActivity.kt에서 'TODO: Swap a ScalingLazyColumn'을 검색합니다. 먼저 LazyColumnScalingLazyColumn으로 대체합니다.

그런 다음 contentPaddingverticalArrangement를 모두 삭제합니다. ScalingLazyColumn은 이미 대부분의 표시 영역이 목록 항목으로 채워지므로 더 나은 기본 시각적 효과를 보장하는 기본 설정을 제공합니다. 대부분의 경우 기본 매개변수로 충분합니다. 상단에 헤더가 있다면 ListHeader에 첫 번째 항목으로 배치하는 것이 좋습니다. 그 외의 경우에는 itemIndex가 0인 autoCentering을 설정하여 첫 번째 항목에 충분한 패딩을 제공하는 것이 좋습니다.

8단계

// TODO: Swap a ScalingLazyColumn (Wear's version of LazyColumn)
ScalingLazyColumn(
    modifier = Modifier.fillMaxSize(),
    autoCentering = AutoCenteringParams(itemIndex = 0),
    state = listState

이제 완료됐습니다. 그 모습을 직접 살펴보겠습니다.

5c25062081307944.png

아주 조금씩 스크롤하면서 이동하면 화면 상단과 하단에서 콘텐츠 크기와 투명도가 조정되는 것을 확인할 수 있습니다.

콘텐츠를 위아래로 움직이면 콘텐츠가 명상 컴포저블과 함께 표시됩니다.

이제 마지막 주제인 Wear OS의 Scaffold로 넘어가겠습니다.

8. Scaffold 추가

Scaffold는 모바일과 같은 일반적인 패턴으로 화면을 정렬할 수 있는 레이아웃 구조를 제공합니다. 하지만, 앱 바, FAB, 창 또는 기타 모바일 관련 요소 대신 최상위 구성요소를 사용하여 Wear에 특화된 네 가지 레이아웃(시간, 비네트, 스크롤/위치 표시기, 페이지 표시기)을 지원합니다.

Scaffold는 원형 기기와 비원형 기기를 모두 처리합니다.

표시되는 모습은 다음과 같습니다.

TimeText

Vignette

PositionIndicator

PageIndicator

처음 세 가지 구성요소를 자세히 살펴보기 전에 먼저 Scaffold를 제자리에 배치해 보겠습니다.

Scaffold 추가

이제 Scaffold의 상용구를 추가해 보겠습니다.

'TODO (Start): Create a Scaffold (Wear Version)'을 찾아 그 아래에 다음 코드를 추가합니다.

9단계

// TODO (Start): Create a Scaffold (Wear Version)
Scaffold(
    timeText = { },
    vignette = { },
    positionIndicator = { }
) {

이러한 각 매개변수에 관해서는 이후 단계에서 순서대로 설명합니다. 지금은 UI를 생성하지 않습니다.

다음으로, 올바른 위치에 오른쪽 괄호를 추가합니다. 'TODO (End): Create a Scaffold (Wear Version)'을 찾고 그곳에 오른쪽 괄호를 추가합니다.

10단계

// TODO (End): Create a Scaffold (Wear Version)
}

먼저 실행해 보겠습니다. 다음과 같이 표시됩니다.

ff554156bbe03abb.png

동작이 Scaffold를 추가하기 전과 사실상 다르지 않다는 것을 알 수 있습니다. 하지만 구성요소를 구현하기 시작하면 동작이 변합니다.

세 매개변수 중 첫 번째인 TimeText부터 시작해 보겠습니다.

TimeText

TimeText는 내부에 곡선 텍스트를 사용합니다. 이 매개변수를 통해 개발자는 컴포저블을 배치하거나 시간 관련 클래스를 사용하지 않고도 손쉽게 시간을 표시할 수 있습니다.

Material 가이드라인에 따라 시간 표시는 앱 내 화면 상단에 하는 것이 좋습니다. 예를 들면 다음과 같습니다.

2a642b9ff3334e2a.png

TimeText를 추가하는 작업은 실제로 매우 간단합니다.

'timeText = { },'를 찾아 아래 코드로 바꾸면 됩니다.

11단계

timeText = {
    TimeText(modifier = Modifier.scrollAway(listState))
},

먼저 TimeText 컴포저블을 만듭니다. 시간 앞이나 뒤에 텍스트를 추가하기 위한 매개변수를 추가할 수 있지만 여기서는 단순하게 만들고자 합니다.

목록과 같은 스크롤 가능한 요소로 TimeText를 만들 때는 사용자가 항목 목록을 위로 스크롤하기 시작하면 TimeText가 뷰에서 점차 사라져야 합니다. 이를 위해 스크롤 상태에 따라 TimeText를 뷰 안팎으로 세로로 스크롤하는 Modifier.scrollAway를 추가합니다.

이제 실행해 봅니다. 이제 시간이 표시되고, 스크롤하면 점차 사라집니다.

43e90952cbcce9b0.png

다음으로 Vignette를 살펴보겠습니다.

비네트 추가

Vignette는 스크롤 가능한 화면이 표시될 경우 웨어러블 화면의 상단과 하단 가장자리를 흐리게 처리합니다.

개발자는 사용 사례에 따라 상단이나 하단 또는 두 곳 모두를 흐리게 처리하도록 지정할 수 있습니다.

예를 들면 다음과 같습니다.

7e85451de59e1d0.png

여기서는 화면(스크롤 가능)이 하나만 있으므로 비네트를 표시해 가독성을 높여보겠습니다. 그럼 시작하겠습니다.

'vignette = { },'를 찾아 아래 코드로 바꿉니다.

12단계

vignette = {
    // Only show a Vignette for scrollable screens. This code lab only has one screen,
    // which is scrollable, so we show it all the time.
    Vignette(vignettePosition = VignettePosition.TopAndBottom)
},

비네트를 표시해야 하는 경우와 그래서는 안 되는 경우의 자세한 내용을 관련 주석에서 확인합니다. 여기서는 비네트를 항상 표시해 화면 상단과 하단을 모두 흐리게 처리하려고 합니다.

f4679e75e295642c.png

상단이나 하단(특히 보라색 컴포저블 부분)에서 그 효과를 볼 수 있습니다.

Scaffold의 마지막 매개변수인 PositionIndicator를 살펴보며 마무리하겠습니다.

PositionIndicator 추가

PositionIndicator(Scrolling Indicator라고도 함)는 화면 오른쪽에 있는 표시기로, 개발자가 전달한 상태 객체의 유형에 따라 현재 표시기 위치를 알려줍니다. 여기서는 ScalingLazyListState입니다.

예를 들면 다음과 같습니다.

ba42dce6b62e720f.png

위치 표시기가 왜 ScalingLazyColumn 수준이 아닌 Scaffold 수준에 나타나야 하는지 의아할 수 있습니다.

화면의 곡선 때문에 위치 표시기는 표시 영역(ScalingLazyColumn)의 중앙이 아닌 시계(Scaffold)의 중앙에 놓여야 합니다. 그러지 않으면 위치 표시기가 잘릴 수 있습니다.

예를 들어 아래 앱에서 'Playlist' 컴포저블은 스크롤 가능한 영역에 속하지 않는다고 볼 수 있습니다. 위치 표시기가 ScalingLazyColumn의 중앙에 놓이지만 전체 화면을 차지하지는 않습니다. 따라서 위치 표시기의 대부분이 잘리는 것을 볼 수 있습니다.

8018e75f709e25a0.png

그러나 대신에 위치 표시기를 표시 가능한 전체 영역(Scaffold에서 제공하는 영역)의 중앙에 배치하면 위치 표시기가 명확히 보입니다.

1a82be61163ead86.png

즉, PositionIndicator에서는 ScalingLazyListState(스크롤 목록에서의 위치를 알려줌)가 Scaffold 위에 있어야 합니다.

특별히 눈썰미가 있다면 시간을 숨기거나 표시할 때 이미 Scaffold 위에 ScalingLazyListState호이스팅한 것을 알아차렸을 것입니다. 따라서 여기서는 이미 그 작업이 이루어졌습니다.

지금까지 위치 표시기가 Scaffold에 있어야 하는 이유를 살펴봤습니다. 이제 위치 표시기를 앱에 추가해 보겠습니다.

'positionIndicator = { }'를 찾아 아래 코드로 바꿉니다.

12단계

positionIndicator = {
    PositionIndicator(
        scalingLazyListState = listState
    )
}

아주 간단합니다. PositionIndicator는 자체적으로 올바로 렌더링되려면 스크롤 상태여야 하는데 이제 가능합니다.

또한 사용자가 스크롤하지 않을 때는 자체적으로 숨겨지는 멋진 기능도 갖게 되었습니다.

여기서는 ScalingLazyListState를 사용하지만 PositionIndicator는 여타 많은 스크롤 옵션을 사용합니다(예: ScrollState, LazyListState). 그리고 회전 측면 버튼이나

회전 베젤까지도 처리할 수 있습니다. 모든 옵션을 보려면 각 버전의 KDocs를 확인하세요.

이제 어떤 모습인지 살펴보겠습니다.

cfcbd3003744a6d.png

위아래로 스크롤해 보세요. 스크롤할 때만 스크롤 표시기가 표시되는 것을 알 수 있습니다.

잘하셨습니다. 대부분의 Wear OS 컴포저블의 UI 데모를 마쳤습니다.

9. 축하합니다

축하합니다. Wear OS에서 Compose를 사용하는 방법에 관한 기본사항을 살펴보았습니다.

이제 모든 Compose 지식을 다시 적용하며 멋진 Wear OS 앱을 만들어 보세요.

다음 단계

다른 Wear OS Codelab을 확인해 보세요.

추가 자료

의견

Wear OS용 Compose의 사용 경험과 이를 사용하여 만들 수 있는 기능에 관한 의견을 들려주세요. Kotlin Slack #compose-wear 채널에서 토론에 참여하고 Issue Tracker에 의견을 계속 제공해 주세요.

즐겁게 코딩해 보세요!