Wear OS용 Compose Codelab

1. 소개

4d28d16f3f514083.png

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

Material You 지원이 내장된 Wear OS용 Compose는 모바일과 마찬가지로 UI 개발을 간소화 및 가속화하며 코딩 작업을 줄이고도 아름다운 앱을 개발할 수 있게 해 줍니다.

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

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

학습할 내용

  • 모바일에 사용되는 Compose와 Wear OS에 사용되는 Compose 간의 유사점과 차이점
  • 모바일에 사용되는 일반 컴포저블과 유사한 Wear OS 컴포저블
  • 새로운 Wear OS 컴포저블
  • Wear OS의 LazyColumn(ScalingLazyColumn))
  • Wear OS의 Scaffold 버전

빌드할 항목

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

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

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

b7bd20036af4859d.gif

기본 요건

2 설정하기

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

준비 사항

  • Android 스튜디오의 최신 정식 버전
  • Wear OS 기기 또는 에뮬레이터(이것을 처음 사용하시나요? 설정하는 방법이 여기에 나와 있습니다.)

코드 다운로드

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

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

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

ZIP 파일 다운로드

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

8a2e49d6d6d2609d.png

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

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

7668d2915856b849.png

시작 코드 살펴보기

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

3. 종속 항목 검토

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

d92519e0b932f964.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

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 mobile version in your Wear OS app.
implementation "androidx.wear.compose:compose-foundation:$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: Similar composables to Mobile ******************* */
            item { ButtonExample(contentModifier, iconModifier) }
            item { TextExample(contentModifier) }
            item { CardExample(contentModifier, iconModifier) }

            /* ********************* Part 2: Wear OS Only 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: Similar composables to Mobile ******************* */
item { ButtonExample(contentModifier, iconModifier) }
item { TextExample(contentModifier) }
item { CardExample(contentModifier, iconModifier) }

/* ********************* Part 2: Wear OS Only 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에 설정합니다.

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

f9fce435c935d610.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 개발자에게 매우 익숙할 것이며, 실제로 코드가 모바일 버전과 동일합니다.

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

52a4e318d12d7ba5.png

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

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

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

27e50afef57b7717.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 = { /* ... */ }
    ) {
        Column(modifier = Modifier.fillMaxWidth()) {
            Text("On my way!")
        }
    }
}

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

여기서는 카드에 Icon이 필요하므로 AppCard를 사용하겠습니다. TitleCard는 텍스트에만 집중합니다.

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

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

dcb5e5bebf9f19d9.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 아이콘을 가져옵니다.

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

77bde81f8f290f87.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,
        toggleIcon = {
            ToggleChipDefaults.SwitchIcon(checked = checked)
        },
        onCheckedChange = {
            checked = it
        },
        label = {
            Text(
                text = "Sound",
                maxLines = 1,
                overflow = TextOverflow.Ellipsis
            )
        }
    )
}

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

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

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

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

다음으로, 원하는 스위치를 구현하기 위해 수정자와 선택한 상태, 전환 아이콘을 설정합니다. 여기서는 기본값을 사용합니다.

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

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

b76b501e91a64969.png

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

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

7 ScalingLazyColumn으로 이전

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

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

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

다음은 데모입니다.

28460354eaf16eea.gif

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

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

307c969dccf58e49.gif

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

ScalingLazyListState의 동작을 확인했으므로 이제 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'을 검색하고 관련 행과 그 아래 행을 다음 코드로 바꿉니다.

8단계

        // TODO: Swap a ScalingLazyColumn (Wear's version of LazyColumn)
        ScalingLazyColumn(

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

264ab059418a6526.png

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

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

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

8 Scaffold 추가

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

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

다음과 같이 구성되어 있습니다.

TimeText

Vignette

PositionIndicator

각 구성요소를 자세히 살펴보겠지만 우선 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)
}

먼저 실행해 보겠습니다. 다음과 같은 것을 확인할 수 있습니다.

2ddc667e07d8f04c.png

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

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

TimeText

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

머티리얼 가이드라인에 따라 시간 표시는 오버레이(또는 앱) 환경 내의 화면 상단에 해야 합니다. 예를 들면 다음과 같은 모습이어야 합니다.

29671ae0a7c3225b.png

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

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

11단계

timeText = {
    if (!listState.isScrollInProgress) {
        TimeText()
    }
},

사용자가 스크롤하기 시작하면 시간을 숨길 수 있는 논리를 추가합니다.

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

이제 실행해 봅니다. 이제 시간이 표시됩니다. 하지만 스크롤하면 시간이 사라집니다.

896aaebb9a89bbe8.png

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

비네트 추가

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

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

다음 예를 참고하세요.

689ac8becaac6824.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)
},

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

9a91313e5d59f49e.png

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

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

PositionIndicator 추가

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

다음 예를 참고하세요.

181b19b55ce37251.png

이제 시작해 볼까요?

특별히 눈썰미가 있다면 Scaffold 위에 ScalingLazyListState호이스팅한 것을 알아차렸을 것입니다.

ScalingLazyColumn 위에 할 수도 있는데 왜 그곳에 호이스팅했는지 궁금할 수 있습니다.

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

예를 들어 아래 앱에서 'Playlist' 컴포저블은 스크롤 가능한 영역에 속하지 않는다고 볼 수 있습니다.

c9f96a90a3e811fb.png

위치 표시기가 화면 중앙에 위치하지 않지만(이 경우처럼) 대신 목록의 표시 영역 중앙에 위치한다면 원형 화면이기 때문에 대부분 잘립니다. 즉, 그곳에 화면이 없습니다.

지금까지 높은 수준으로 호이스팅한 이유를 설명했습니다. 이제 앱에 추가해 보겠습니다.

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

12단계

positionIndicator = {
    PositionIndicator(
        scalingLazyListState = listState
    )
}

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

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

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

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

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

8c278b78ab008cbb.png

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

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

9. 축하합니다

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

이제 모바일 버전에서 배운 모든 지식을 다시 적용하며 멋진 Wear OS 앱을 만들어 보세요.

다음 단계

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

추가 자료