카드 버전 관리

Wear OS 기기에서 타일은 독립적인 버전 관리가 적용된 두 가지 주요 구성요소에 의해 렌더링됩니다. 앱 타일이 모든 기기에서 올바르게 작동하도록 하려면 이 기본 아키텍처를 이해하는 것이 중요합니다.

  • Jetpack 타일 관련 라이브러리: 이러한 라이브러리 (Wear 타일 및 Wear ProtoLayout 포함)는 앱에 삽입되며 개발자가 버전을 관리합니다. 앱은 이러한 라이브러리를 사용하여 시스템의 onTileRequest() 호출에 대한 응답으로 TileBuilder.Tile 객체 (타일을 나타내는 데이터 구조)를 구성합니다.
  • ProtoLayout 렌더러: 이 시스템 구성요소는 디스플레이에 Tile 객체를 렌더링하고 사용자 상호작용을 처리합니다. 렌더러 버전은 앱 개발자가 제어하지 않으며 동일한 하드웨어를 사용하는 기기 간에도 다를 수 있습니다.

타일의 모양이나 동작은 앱의 Jetpack 타일 라이브러리 버전과 사용자 기기의 ProtoLayout 렌더러 버전에 따라 달라질 수 있습니다. 예를 들어 한 기기는 회전이나 심박수 데이터 표시를 지원하지만 다른 기기는 지원하지 않을 수 있습니다.

이 문서에서는 앱이 다양한 버전의 타일 라이브러리 및 ProtoLayout 렌더러와 호환되는지 확인하는 방법과 더 높은 Jetpack 라이브러리 버전으로 이전하는 방법을 설명합니다.

호환성 고려

다양한 기기에서 올바르게 작동하는 타일을 만들려면 다양한 기능 지원을 고려하세요. 런타임에 렌더러 기능을 감지하고 내장 대체를 제공하는 두 가지 주요 전략을 통해 이 작업을 수행할 수 있습니다.

렌더러 기능 감지

특정 기기에서 사용할 수 있는 기능에 따라 타일의 레이아웃을 동적으로 변경할 수 있습니다.

렌더러 버전 감지

  • onTileRequest() 메서드에 전달된 DeviceParameters 객체의 getRendererSchemaVersion() 메서드를 사용합니다. 이 메서드는 기기의 ProtoLayout 렌더러의 주 버전 번호와 부 버전 번호를 반환합니다.
  • 그런 다음 onTileRequest() 구현에서 조건부 논리를 사용하여 감지된 렌더러 버전에 따라 타일의 디자인이나 동작을 조정할 수 있습니다.

@RequiresSchemaVersion 주석

  • ProtoLayout 메서드의 @RequiresSchemaVersion 주석은 해당 메서드가 문서화된 대로 작동하는 데 필요한 최소 렌더러 스키마 버전을 나타냅니다 ().
    • 기기에서 사용할 수 있는 것보다 높은 렌더러 버전이 필요한 메서드를 호출해도 앱이 비정상 종료되지는 않지만 콘텐츠가 표시되지 않거나 기능이 무시될 수 있습니다.

버전 감지 예

val rendererVersion = requestParams.deviceConfiguration.rendererSchemaVersion

val arcElement =
    // DashedArcLine has the annotation @RequiresSchemaVersion(major = 1, minor = 500)
    // and so is supported by renderer versions 1.500 and greater
    if (
        rendererVersion.major > 1 ||
        (rendererVersion.major == 1 && rendererVersion.minor >= 500)
    ) {
        // Use DashedArcLine if the renderer supports it …
        DashedArcLine.Builder()
            .setLength(degrees(270f))
            .setThickness(8f)
            .setLinePattern(
                LayoutElementBuilders.DashedLinePattern.Builder()
                    .setGapSize(8f)
                    .setGapInterval(10f)
                    .build()
            )
            .build()
    } else {
        // … otherwise use ArcLine.
        ArcLine.Builder().setLength(degrees(270f)).setThickness(dp(8f)).build()
    }

대체 제공

일부 리소스에서는 빌더에서 직접 대체를 정의할 수 있습니다. 이는 렌더러 버전을 확인하는 것보다 간단한 경우가 많으며, 사용 가능한 경우 선호되는 접근 방식입니다.

일반적인 사용 사례는 Lottie 애니메이션의 대체로 정적 이미지를 제공하는 것입니다. 기기에서 Lottie 애니메이션을 지원하지 않으면 정적 이미지가 대신 렌더링됩니다.

val lottieImage =
    ResourceBuilders.ImageResource.Builder()
        .setAndroidLottieResourceByResId(
            ResourceBuilders.AndroidLottieResourceByResId.Builder(R.raw.lottie)
                .setStartTrigger(createOnVisibleTrigger())
                .build()
        )
        // Fallback if lottie is not supported
        .setAndroidResourceByResId(
            ResourceBuilders.AndroidImageResourceByResId.Builder()
                .setResourceId(R.drawable.lottie_fallback)
                .build()
        )
        .build()

다양한 렌더러 버전으로 테스트

다양한 렌더러 버전을 대상으로 타일을 테스트하려면 다양한 버전의 Wear OS 에뮬레이터에 타일을 배포하세요. (실제 기기에서는 ProtoLayout 렌더러 업데이트가 Play 스토어 또는 시스템 업데이트를 통해 제공됩니다. 특정 렌더러 버전의 설치를 강제할 수는 없습니다.)

Android 스튜디오의 타일 미리보기 기능은 코드가 종속된 Jetpack ProtoLayout 라이브러리에 삽입된 렌더러를 사용하므로 타일을 테스트할 때 다른 Jetpack 라이브러리 버전에 종속되는 방법도 있습니다.

타일 1.5 / ProtoLayout 1.3 (Material 3 Expressive)으로 이전

Jetpack 타일 라이브러리를 업데이트하여 타일이 시스템과 원활하게 통합되도록 하는 UI 변경사항을 비롯한 최신 개선사항을 활용하세요.

Jetpack Tiles 1.5와 Jetpack ProtoLayout 1.3에는 몇 가지 주목할 만한 개선사항과 변경사항이 도입되었습니다. 예를 들면 다음과 같습니다.

  • UI를 설명하는 Compose와 유사한 API
  • 하단에 고정된 가장자리 버튼, 향상된 시각적 요소(Lottie 애니메이션, 더 많은 그라데이션 유형, 새로운 호선 스타일) 지원 등 Material 3 Expressive 구성요소 - 참고: 이러한 기능 중 일부는 새 API로 이전하지 않고도 사용할 수 있습니다.

권장사항

  • 모든 타일을 동시에 이전합니다. 앱 내에서 타일 버전을 혼합하지 마세요. Material 3 구성요소는 별도의 아티팩트(androidx.wear.protolayout:protolayout-material3)에 있으므로 기술적으로는 동일한 앱에서 M2.5 및 M3 타일을 모두 사용할 수 있지만, 앱에 한 번에 모두 이전할 수 없는 타일이 많이 있는 경우와 같이 절대적으로 필요한 경우가 아니라면 이 방법을 사용하지 않는 것이 좋습니다.
  • 카드 UX 가이드 채택 카드는 고도로 구조화되어 있고 템플릿화되어 있으므로 기존 샘플의 디자인을 자체 디자인의 시작점으로 사용하세요.
  • 다양한 화면 및 글꼴 크기로 테스트합니다. 타일은 정보가 밀집되어 있는 경우가 많으므로 텍스트 (특히 버튼에 배치된 경우)가 오버플로되거나 잘릴 수 있습니다. 이를 최소화하려면 사전 빌드된 구성요소를 사용하고 광범위한 맞춤설정을 피하세요. Android 스튜디오의 타일 미리보기 기능을 사용하여 테스트하고 여러 실제 기기에서도 테스트합니다.

마이그레이션 프로세스

종속 항목 업데이트

먼저 build.gradle.kts 파일을 업데이트합니다. 다음과 같이 버전을 업데이트하고 protolayout-material 종속 항목을 protolayout-material3로 변경합니다.

// In build.gradle.kts

//val tilesVersion = "1.4.1"
//val protoLayoutVersion = "1.2.1"

// Use these versions for M3.
val tilesVersion = "1.5.0-rc01"
val protoLayoutVersion = "1.3.0-rc01"

 dependencies {
     // Use to implement support for wear tiles
     implementation("androidx.wear.tiles:tiles:$tilesVersion")

     // Use to utilize standard components and layouts in your tiles
     implementation("androidx.wear.protolayout:protolayout:$protoLayoutVersion")

     // Use to utilize components and layouts with Material Design in your tiles
     // implementation("androidx.wear.protolayout:protolayout-material:$protoLayoutVersion")
     implementation("androidx.wear.protolayout:protolayout-material3:$protoLayoutVersion")

     // Use to include dynamic expressions in your tiles
     implementation("androidx.wear.protolayout:protolayout-expression:$protoLayoutVersion")

     // Use to preview wear tiles in your own app
     debugImplementation("androidx.wear.tiles:tiles-renderer:$tilesVersion")

     // Use to fetch tiles from a tile provider in your tests
     testImplementation("androidx.wear.tiles:tiles-testing:$tilesVersion")
 }

TileService는 크게 변경되지 않음

이 이전의 주요 변경사항은 UI 구성요소에 영향을 미칩니다. 따라서 리소스 로드 메커니즘을 비롯한 TileService 구현에는 최소한의 수정만 필요하거나 수정이 필요하지 않습니다.

주요 예외는 타일 활동 추적과 관련이 있습니다. 앱에서 onTileEnterEvent() 또는 onTileLeaveEvent()를 사용하는 경우 onRecentInteractionEventsAsync()로 이전해야 합니다. API 36부터 이러한 이벤트는 일괄 처리됩니다.

레이아웃 생성 코드 적용

ProtoLayout 1.2 (M2.5)에서 onTileRequest() 메서드는 TileBuilders.Tile를 반환합니다. 이 객체에는 TimelineBuilders.Timeline이 포함되어 있었으며, 이 객체에는 타일의 UI를 설명하는 LayoutElement이 포함되어 있었습니다.

ProtoLayout 1.3(M3)에서는 전체 데이터 구조와 흐름은 변경되지 않았지만 이제 LayoutElement이 정의된 슬롯을 기반으로 하는 레이아웃을 사용하여 Compose에서 영감을 받은 접근 방식으로 구성됩니다. 슬롯은 위에서 아래로 titleSlot(선택사항, 일반적으로 기본 제목 또는 헤더용), mainSlot(필수사항, 핵심 콘텐츠용), bottomSlot(선택사항, 가장자리 버튼과 같은 작업이나 짧은 텍스트와 같은 보조 정보용)입니다. 이 레이아웃은 primaryLayout() 함수에 의해 구성됩니다.

mainSlot, titleSlot, bottomSlot을 표시하는 타일의 레이아웃
그림 1.: 타일의 슬롯입니다.
레이아웃 M2.5와 M3 레이아웃 함수 비교

M2.5

fun myLayout(
    context: Context,
    deviceConfiguration: DeviceParametersBuilders.DeviceParameters
) =
    PrimaryLayout.Builder(deviceConfiguration)
        .setResponsiveContentInsetEnabled(true)
        .setContent(
            Text.Builder(context, "Hello World!")
                .setTypography(Typography.TYPOGRAPHY_BODY1)
                .setColor(argb(0xFFFFFFFF.toInt()))
                .build()
        )
        .build()

M3

fun myLayout(
    context: Context,
    deviceConfiguration: DeviceParametersBuilders.DeviceParameters,
) =
    materialScope(context, deviceConfiguration) {
        primaryLayout(mainSlot = { text("Hello, World!".layoutString) })
    }

주요 차이점을 강조하기 위해 다음을 참고하세요.

  1. Builders 삭제 Material3 UI 구성요소의 기존 빌더 패턴은 더 선언적이고 Compose에서 영감을 받은 구문으로 대체됩니다. (문자열/색상/수정자와 같은 UI가 아닌 구성요소에도 새로운 Kotlin 래퍼가 제공됩니다.)
  2. 표준화된 초기화 및 레이아웃 함수 M3 레이아웃은 표준화된 초기화 및 구조 함수인 materialScope()primaryLayout()를 사용합니다. 이러한 필수 함수는 M3 환경 (테마, materialScope를 통한 구성요소 범위)을 초기화하고 기본 슬롯 기반 레이아웃 (primaryLayout를 통해)을 정의합니다. 둘 다 레이아웃당 정확히 한 번 호출해야 합니다.

테마 설정

색상

Material 3 Expressive의 눈에 띄는 기능은 '동적 테마'입니다. 이 기능을 사용 설정하는 타일 (기본적으로 사용 설정됨)은 시스템 제공 테마로 표시됩니다(사용 가능 여부는 사용자의 기기 및 구성에 따라 다름).

M3의 또 다른 변경사항은 색상 토큰 수가 4개에서 29개로 증가한 것입니다. 새 색상 토큰은 ColorScheme 클래스에서 확인할 수 있습니다.

서체

M2.5와 마찬가지로 M3는 사전 정의된 글꼴 크기 상수를 많이 사용하므로 글꼴 크기를 직접 지정하는 것은 권장되지 않습니다. 이러한 상수는 Typography 클래스에 있으며 좀 더 표현력이 풍부한 옵션을 약간 더 넓은 범위로 제공합니다.

자세한 내용은 서체 문서를 참고하세요.

도형

대부분의 M3 구성요소는 색상뿐만 아니라 모양에 따라 달라질 수 있습니다.

모양이 fulltextButton (mainSlot에 있음)

'전체' 모양 (더 둥근 모서리)이 있는 타일
그림 2.: '전체' 모양의 타일

모양이 small인 동일한 textButton:

'작은' 모양의 타일 (둥근 모서리가 적음)
그림 3.: '작은' 모양의 타일

구성요소

M3 구성요소는 M2.5 구성요소보다 훨씬 더 유연하고 구성 가능합니다. M2.5에서는 다양한 시각적 처리를 위해 별도의 구성요소가 필요한 경우가 많았지만 M3에서는 기본값이 우수하고 구성 가능성이 높은 일반화된 '기본' 구성요소를 자주 사용합니다.

이 원칙은 '루트' 레이아웃에 적용됩니다. M2.5에서는 PrimaryLayout 또는 EdgeContentLayout였습니다. M3에서는 단일 최상위 MaterialScope가 설정된 후 primaryLayout() 함수가 호출됩니다. 이렇게 하면 루트 레이아웃이 직접 반환되고 (빌더 필요 없음) titleSlot, mainSlot, bottomSlot과 같은 여러 '슬롯'에 LayoutElements가 허용됩니다. 이러한 슬롯은 text(), button(), card()에서 반환되는 구체적인 UI 구성요소나 LayoutElementBuildersRow 또는 Column과 같은 레이아웃 구조로 채울 수 있습니다.

테마는 또 다른 주요 M3 개선사항입니다. 기본적으로 UI 요소는 M3 스타일 지정 사양을 자동으로 준수하고 동적 테마를 지원합니다.

M2.5 M3
상호작용 요소
Button 또는 Chip
텍스트
Text text()
진행 상태 표시기
CircularProgressIndicator circularProgressIndicator() 또는 segmentedCircularProgressIndicator()
레이아웃
PrimaryLayout 또는 EdgeContentLayout primaryLayout()
buttonGroup()
이미지
icon(), avatarImage() 또는 backgroundImage()

수정자

M3에서 구성요소를 장식하거나 강화하는 데 사용하는 Modifiers는 Compose와 더 유사합니다. 이 변경사항은 적절한 내부 유형을 자동으로 구성하여 상용구를 줄일 수 있습니다. (이 변경사항은 M3 UI 구성요소 사용과 관련이 없습니다. 필요한 경우 ProtoLayout 1.2의 빌더 스타일 수정자를 M3 UI 구성요소와 함께 사용할 수 있으며 그 반대도 가능합니다.)

M2.5

// A Builder-style modifier to set the opacity of an element to 0.5
fun myModifier(): ModifiersBuilders.Modifiers =
    ModifiersBuilders.Modifiers.Builder()
        .setOpacity(TypeBuilders.FloatProp.Builder(0.5F).build())
        .build()

M3

// The equivalent Compose-like modifier is much simpler
fun myModifier(): LayoutModifier = LayoutModifier.opacity(0.5F)

API 스타일을 사용하여 수정자를 구성할 수 있으며 toProtoLayoutModifiers() 확장 함수를 사용하여 LayoutModifierModifiersBuilders.Modifier로 변환할 수도 있습니다.

도우미 함수

ProtoLayout 1.3에서는 Compose에서 영감을 받은 API를 사용하여 많은 UI 구성요소를 표현할 수 있지만 LayoutElementBuilders과 같은 기본 레이아웃 요소는 빌더 패턴을 계속 사용합니다. 이러한 스타일 격차를 해소하고 새로운 M3 구성요소 API와의 일관성을 높이려면 도우미 함수를 사용하는 것이 좋습니다.

도우미 없음

primaryLayout(
    mainSlot = {
        LayoutElementBuilders.Column.Builder()
            .setWidth(expand())
            .setHeight(expand())
            .addContent(text("A".layoutString))
            .addContent(text("B".layoutString))
            .addContent(text("C".layoutString))
            .build()
    }
)

도우미 포함

// Function literal with receiver helper function
fun column(builder: Column.Builder.() -> Unit) =
    Column.Builder().apply(builder).build()

primaryLayout(
    mainSlot = {
        column {
            setWidth(expand())
            setHeight(expand())
            addContent(text("A".layoutString))
            addContent(text("B".layoutString))
            addContent(text("C".layoutString))
        }
    }
)

타일 1.2 / ProtoLayout 1.0으로 이전

버전 1.2부터 대부분의 Tiles Layout API는 androidx.wear.protolayout 네임스페이스에 있습니다. 최신 API를 사용하려면 코드에서 아래와 같은 이전 단계를 완료하세요.

종속 항목 업데이트

앱 모듈의 빌드 파일에서 다음과 같이 변경합니다.

Groovy

  // Remove
  implementation 'androidx.wear.tiles:tiles-material:version'

  // Include additional dependencies
  implementation "androidx.wear.protolayout:protolayout:1.3.0"
  implementation "androidx.wear.protolayout:protolayout-material:1.3.0"
  implementation "androidx.wear.protolayout:protolayout-expression:1.3.0"

  // Update
  implementation "androidx.wear.tiles:tiles:1.5.0"

Kotlin

  // Remove
  implementation("androidx.wear.tiles:tiles-material:version")

  // Include additional dependencies
  implementation("androidx.wear.protolayout:protolayout:1.3.0")
  implementation("androidx.wear.protolayout:protolayout-material:1.3.0")
  implementation("androidx.wear.protolayout:protolayout-expression:1.3.0")

  // Update
  implementation("androidx.wear.tiles:tiles:1.5.0")

네임스페이스 업데이트

앱의 Kotlin 및 Java 기반 코드 파일에서 다음과 같이 업데이트합니다. 또는 네임스페이스 이름 변경 스크립트를 실행할 수 있습니다.

  1. 모든 androidx.wear.tiles.material.* 가져오기를 androidx.wear.protolayout.material.*로 바꿉니다. androidx.wear.tiles.material.layouts 라이브러리에서도 이 단계를 완료합니다.
  2. 대부분의 나머지 androidx.wear.tiles.* 가져오기를 androidx.wear.protolayout.*으로 바꿉니다.

    androidx.wear.tiles.EventBuilders, androidx.wear.tiles.RequestBuilders, androidx.wear.tiles.TileBuilders, androidx.wear.tiles.TileService의 가져오기는 동일하게 유지해야 합니다.

  3. TileService 및 TileBuilder 클래스의 지원 중단된 몇 가지 메서드의 이름을 바꿉니다.

    1. TileBuilders: getTimeline()에서 getTileTimeline()으로, setTimeline()에서 setTileTimeline()으로
    2. TileService: onResourcesRequest()에서 onTileResourcesRequest()
    3. RequestBuilders.TileRequest: getDeviceParameters()에서 getDeviceConfiguration()으로, setDeviceParameters()에서 setDeviceConfiguration()으로, getState()에서 getCurrentState()로, setState()에서 setCurrentState()