1. 소개
Wear OS 카드는 사용자가 작업 처리에 필요한 정보와 동작에 쉽게 액세스할 수 있게 해 줍니다. 사용자는 워치 페이스에서 간단히 스와이프하여 최신 일기예보를 찾거나 타이머를 시작할 수 있습니다.
카드는 자체 애플리케이션 컨테이너에서 실행되지 않고 시스템 UI의 일부로 실행됩니다. Service를 사용하여 카드의 레이아웃과 콘텐츠를 설명합니다. 그러면 시스템 UI가 필요에 따라 카드를 렌더링합니다.
실행할 작업
최근 대화를 표시하는 메시지 앱의 카드를 빌드합니다. 이 표시 영역에서 사용자는 다음 3가지 일반적인 작업 중 하나로 바로 이동할 수 있습니다.
- 대화 열기
- 새 메시지 쓰기
학습할 내용
이 Codelab에서는 다음을 포함하여 자체 Wear OS 카드를 작성하는 방법을 알아봅니다.
TileService
를 만드는 방법- 기기에서 카드를 테스트하는 방법
- Android 스튜디오에서 카드 UI를 미리 보는 방법
- 카드 UI를 개발하는 방법
- 이미지 추가
- 상호작용 처리
기본 요건
- Kotlin에 관한 기본적 이해
2. 설정
이 단계에서는 환경을 설정하고 시작 프로젝트를 다운로드해 보겠습니다.
필요한 항목
- Android 스튜디오 Koala 기능 출시 | 2024.1.2 Canary 1 및 이후 버전
- Wear OS 기기 또는 에뮬레이터
Wear OS 사용에 익숙하지 않은 경우 시작하기 전에 이 빠른 가이드를 읽어보는 것이 좋습니다. Wear OS 에뮬레이터 설정에 관한 안내가 포함되어 있으며 시스템 탐색 방법을 설명합니다.
코드 다운로드
git이 설치되어 있으면 아래 명령어를 실행하여 이 저장소의 코드를 클론하면 됩니다.
git clone https://github.com/android/codelab-wear-tiles.git cd codelab-wear-tiles
git이 없는 경우 다음 버튼을 클릭하여 이 Codelab을 위한 모든 코드를 다운로드할 수 있습니다.
Android 스튜디오에서 프로젝트 열기
'Welcome to Android Studio' 창에서 Open an Existing Project 또는 File > Open을 선택하고 [Download Location] 폴더를 선택합니다.
3. 기본 카드 만들기
카드의 진입점은 카드 서비스입니다. 이 단계에서는 카드 서비스를 등록하고 카드의 레이아웃을 정의합니다.
HelloWorldTileService
TileService
를 구현하는 클래스는 다음 두 메서드를 지정해야 합니다.
onTileResourcesRequest(requestParams: ResourcesRequest): ListenableFuture<Resources>
onTileRequest(requestParams: TileRequest): ListenableFuture<Tile>
첫 번째 메서드는 문자열 ID를 카드에서 사용할 이미지 리소스에 매핑하는 Resources
객체를 반환합니다.
두 번째 메서드는 레이아웃을 포함하여 카드에 관한 설명을 반환합니다. 여기에서 카드의 레이아웃과 데이터가 이 레이아웃에 결합되는 방법을 정의합니다.
start
모듈에서 HelloWorldTileService.kt
를 엽니다. 모든 변경사항은 이 모듈에 적용됩니다. 이 Codelab의 결과를 확인하려는 경우 finished
모듈도 있습니다.
HelloWorldTileService
는 Horologist 카드 라이브러리의 Kotlin 코루틴 친화적인 래퍼 SuspendingTileService
를 확장합니다. Horologist는 Google에서 제공하는 라이브러리 그룹으로, 개발자에게 일반적으로 필요하지만 아직 Jetpack에서 사용할 수 없는 기능을 제공하여 Wear OS 개발자를 돕는 것을 목표로 합니다.
SuspendingTileService
는 TileService
의 코루틴 버전 함수인 정지 함수 두 개를 제공합니다.
suspend resourcesRequest(requestParams: ResourcesRequest): Resources
suspend tileRequest(requestParams: TileRequest): Tile
코루틴에 관한 자세한 내용은 Android의 Kotlin 코루틴 문서를 참고하세요.
HelloWorldTileService
가 아직 완료되지 않았습니다. 매니페스트에 서비스를 등록해야 하며 tileLayout
의 구현도 제공해야 합니다.
카드 서비스 등록
매니페스트에 카드 서비스가 등록되면 사용자가 추가할 수 있도록 카드 목록에 표시됩니다.
<application>
요소 내에 <service>
를 추가합니다.
start/src/main/AndroidManifest.xml
<service
android:name="com.example.wear.tiles.hello.HelloWorldTileService"
android:icon="@drawable/ic_waving_hand_24"
android:label="@string/hello_tile_label"
android:description="@string/hello_tile_description"
android:exported="true"
android:permission="com.google.android.wearable.permission.BIND_TILE_PROVIDER">
<intent-filter>
<action android:name="androidx.wear.tiles.action.BIND_TILE_PROVIDER" />
</intent-filter>
<!-- The tile preview shown when configuring tiles on your phone -->
<meta-data
android:name="androidx.wear.tiles.PREVIEW"
android:resource="@drawable/tile_hello" />
</service>
카드가 처음 로드될 때, 또는 카드 로드 중 오류가 발생하면, 아이콘과 라벨이 자리표시자로 사용됩니다. 끝에 있는 meta-data는 사용자가 카드를 추가할 때 캐러셀에 표시되는 미리보기 이미지를 정의합니다.
카드 레이아웃 정의
HelloWorldTileService
에는 TODO()
가 본문으로 있는 tileLayout
이라는 함수가 있습니다. 이제 이 함수를 카드의 레이아웃을 정의하는 구현으로 대체해 보겠습니다.
start/src/main/java/com/example/wear/tiles/hello/HelloWorldTileService.kt
fun tileLayout(
context: Context,
deviceConfiguration: DeviceParametersBuilders.DeviceParameters,
message: String,
) =
materialScope(
context = context,
deviceConfiguration = deviceConfiguration,
allowDynamicTheme = false,
) {
primaryLayout(mainSlot = { text(message.layoutString) })
}
첫 번째 Wear OS 카드를 만들었습니다. 이 카드를 설치하고 어떻게 표시되는지 살펴보겠습니다.
4. 기기에서 카드 테스트
실행 구성 드롭다운에서 시작 모듈을 선택하면 기기나 에뮬레이터에 앱(start
모듈)을 설치하고 사용자와 마찬가지로 카드를 수동으로 설치할 수 있습니다.
하지만 Android 스튜디오에는 이를 위한 바로가기가 있습니다. 여백에서 '서비스 실행' 아이콘(▷)을 탭한 다음 'Run 'HelloWorldTileService''를 선택하면 연결된 기기에 카드가 설치되고 실행됩니다.
'Run ‘HelloWorldTileService''를 선택하여 연결된 기기에서 카드를 빌드하고 실행합니다. 아래 스크린샷과 같이 표시됩니다.
디스플레이 상단에 표시되는 '손 흔들기' 아이콘은 시스템에서 제공합니다. 이를 변경하려면 매니페스트에서 카드의 <service>
요소의 android:icon
속성을 수정합니다.
편의를 위해 이 프로세스에서는 나중에 사용할 'HelloWorldTileService' '실행 구성'도 만듭니다.
5. 미리보기 함수 추가
Android 스튜디오에서는 카드 UI를 미리 볼 수 있습니다. 이렇게 하면 UI를 개발할 때 피드백 루프가 단축되어 개발 속도가 빨라집니다.
HelloWorldTileService.kt
파일 끝에 HelloWorldTileService
의 카드 미리보기를 추가합니다.
start/src/main/java/com/example/wear/tiles/hello/HelloWorldTileService.kt
@Preview(device = WearDevices.SMALL_ROUND, name = "Small Round")
@Preview(device = WearDevices.LARGE_ROUND, name = "Large Round")
internal fun helloLayoutPreview(context: Context): TilePreviewData {
return TilePreviewData {
TilePreviewHelper.singleTimelineEntryTileBuilder(
helloLayout(context, it.deviceConfiguration, "Hello, preview tile!")
)
.build()
}
}
'Split' 편집기 모드를 사용하여 카드의 미리보기를 확인합니다.
@Composable
주석은 사용되지 않습니다. 카드는 컴포저블 함수와 동일한 미리보기 UI를 사용하지만 Compose를 사용하지 않으며 컴포저블이 아닙니다.
6. 메시지 카드 빌드
빌드할 메시지 카드는 실제 카드와 더 비슷합니다. HelloWorld 예와 달리 이 예에서는 Material 3 Expressive 구성요소를 보여주고, 이미지를 표시하고, 앱을 여는 상호작용을 처리합니다.
MessagingTileService
MessagingTileService
는 앞에서 본 SuspendingTileService
클래스를 확장합니다.
7. UI 구성요소 추가
ProtoLayout 라이브러리는 사전 빌드된 구성요소와 레이아웃을 제공하므로 Wear OS용 최신 Material 3 Expressive 디자인을 수용하는 카드를 만들 수 있습니다.
Tiles Material 종속 항목을 build.gradle
파일에 추가합니다.
start/build.gradle
implementation "androidx.wear.protolayout:protolayout-material3:$protoLayoutVersion"
tileLayout()
함수에 materialScope()
함수의 본문으로 레이아웃 코드를 추가합니다. 이렇게 하면 행 2개(각각 버튼 2개 포함)와 가장자리 버튼으로 구성된 레이아웃이 생성됩니다.
'TODO() // Add primaryLayout()' 줄을 찾아 아래 코드로 바꿉니다.
start/src/main/java/com/example/wear/tiles/messaging/tile/Layout.kt
primaryLayout(
mainSlot = {
// This layout code assumes "contacts" contains at least 4 elements, for sample code
// that can handle an arbitrary number of contacts, and also shows different numbers
// of contacts based on the physical screen size, see
// <https://github.com/android/wear-os-samples/tree/main/WearTilesKotlin>.
Column.Builder()
.apply {
setWidth(expand())
setHeight(expand())
addContent(
buttonGroup {
buttonGroupItem { contactButton(contacts[0]) }
buttonGroupItem { contactButton(contacts[1]) }
}
)
addContent(DEFAULT_SPACER_BETWEEN_BUTTON_GROUPS)
addContent(
buttonGroup {
buttonGroupItem { contactButton(contacts[2]) }
buttonGroupItem { contactButton(contacts[3]) }
}
)
}
.build()
},
bottomSlot = {
textEdgeButton(
onClick = clickable(), // TODO: Launch new conversation activity
labelContent = { text("New".layoutString) },
)
},
)
동일한 파일의 contactButton()
함수는 개별 연락처 버튼을 만듭니다. 연락처에 연결된 이미지가 있는 경우 버튼에 이미지가 표시됩니다. 이미지가 없는 경우 연락처의 이니셜이 사용됩니다.
이 시점에서 일반적인 레이아웃은 올바르지만 이미지가 누락된 것을 확인할 수 있습니다.
기기에 카드를 배포해도 동일한 결과가 표시됩니다.
다음 단계에서는 누락된 이미지를 수정합니다.
8. 이미지 추가
카드는 크게 두 가지 요소로 구성됩니다. 바로 (문자열 ID로 리소스를 참조하는) 레이아웃과 리소스 자체(이미지일 수 있음)입니다.
현재 코드는 레이아웃을 제공하지만 리소스 자체는 제공하지 않습니다. 미리보기를 수정하려면 이미지 '리소스'를 제공해야 합니다. 이렇게 하려면 'TODO: Add onTileResourceRequest'를 찾아 아래 코드를 이름이 지정된 인수로 TilePreviewData()
에 추가합니다.
start/src/main/java/com/example/wear/tiles/messaging/tile/Layout.kt
// Additional named argument to TilePreviewData
onTileResourceRequest = { resourcesRequest ->
Resources.Builder()
.setVersion(resourcesRequest.version)
.apply {
contacts.forEach {
if (it.avatarSource is AvatarSource.Resource) {
addIdToImageMapping(
it.imageResourceId(),
it.avatarSource.resourceId
)
}
}
}
.build()
}
이제 이미지가 미리보기에 표시됩니다.
하지만 카드가 기기에 배포되면 이미지가 누락됩니다. 이 문제를 해결하려면 Service.kt
의 resourcesRequest()
함수를 다음과 같이 바꿉니다.
start/src/main/java/com/example/wear/tiles/messaging/tile/Service.kt
override suspend fun resourcesRequest(
requestParams: ResourcesRequest
): Resources {
// resourceIds is a list of the ids we need to provide images for. If we're passed an empty
// list, set resourceIds to all resources.
val resourceIds =
requestParams.resourceIds.ifEmpty {
contacts.map { it.imageResourceId() }
}
// resourceMap maps (tile) resource ids to (Android) resource ids.
val resourceMap =
contacts
.mapNotNull {
when (it.avatarSource) {
is AvatarSource.Resource ->
it.imageResourceId() to
it.avatarSource.resourceId
else -> null
}
}
.toMap()
.filterKeys {
it in resourceIds
} // filter to only the resources we need
// Add images in the resourceMap to the Resources object, and return the result.
return Resources.Builder()
.setVersion(requestParams.version)
.apply {
resourceMap.forEach { (id, imageResource) ->
addIdToImageMapping(id, imageResource)
}
}
.build()
}
이제 카드가 기기에 배포될 때도 이미지가 표시됩니다.
다음 단계에서는 각 요소에 대한 클릭을 처리합니다.
9. 상호작용 처리
카드로 할 수 있는 가장 유용한 작업 중 하나는 중요한 사용자 여정에 대한 바로가기를 제공하는 것입니다. 이는 단순히 앱을 여는 앱 런처와는 다릅니다. 여기서는 앱의 특정 화면에 컨텍스트 바로가기를 제공할 공간이 있습니다.
지금까지는 인수가 없는 clickable()
에서 제공하는 더미 작업을 칩과 각 버튼에 사용했습니다. 이는 대화형이 아닌 미리보기에는 적합하지만 이제는 요소의 작업을 추가하는 방법을 살펴보겠습니다.
LaunchAction
LaunchAction
은 활동을 시작하는 데 사용할 수 있습니다. 'New' 버튼을 탭하여 '새 대화' 사용자 여정을 시작하도록 Layout
을 수정하겠습니다.
'TODO: Launch new conversation activity' 줄을 찾아 clickable()
을 다음으로 대체합니다.
start/src/main/java/com/example/wear/tiles/messaging/tile/Layout.kt
clickable(
id = "new_button",
action =
launchAction(
ComponentName(
"com.example.wear.tiles",
"com.example.wear.tiles.messaging.MainActivity",
),
mapOf(
MainActivity.EXTRA_JOURNEY to
ActionBuilders.stringExtra(
MainActivity.EXTRA_JOURNEY_NEW
)
),
),
)
카드를 다시 배포합니다. 이제 아무것도 하지 않는 대신 'New'를 탭하면 MainActivity
가 실행되고 '새 대화' 사용자 여정이 시작됩니다.
마찬가지로 연락처 버튼을 탭하면 특정 사용자와의 대화가 시작되도록 Layout
을 수정합니다.
'Launch open conversation activity' 줄을 찾아 clickable()
을 다음으로 대체합니다.
start/src/main/java/com/example/wear/tiles/messaging/tile/Layout.kt
clickable(
id = contact.id.toString(),
action =
launchAction(
ComponentName(
"com.example.wear.tiles",
"com.example.wear.tiles.messaging.MainActivity",
),
mapOf(
MainActivity.EXTRA_JOURNEY to
ActionBuilders.stringExtra(
MainActivity
.EXTRA_JOURNEY_CONVERSATION
),
MainActivity.EXTRA_CONVERSATION_CONTACT to
ActionBuilders.stringExtra(
contact.name
),
),
),
)
카드를 다시 배포합니다. 이제 아무것도 하지 않는 대신 연락처를 탭하면 해당 연락처와 대화가 시작됩니다.
10. 축하합니다
축하합니다. 지금까지 Wear OS용 카드를 빌드하는 방법을 알아보았습니다.
다음 단계는 무엇일까요?
자세한 내용은 GitHub의 Golden Tiles 구현, Wear OS 카드 가이드, 디자인 가이드라인을 참고하세요.