카드 시작하기

앱에서 카드를 제공하려면 앱의 build.gradle 파일에 다음 종속 항목을 포함합니다.

Groovy

dependencies {
    // Use to implement support for wear tiles
    implementation "androidx.wear.tiles:tiles:1.3.0"

    // Use to utilize standard components and layouts in your tiles
    implementation "androidx.wear.protolayout:protolayout:1.1.0"

    // Use to utilize components and layouts with Material Design in your tiles
    implementation "androidx.wear.protolayout:protolayout-material:1.1.0"

    // Use to include dynamic expressions in your tiles
    implementation "androidx.wear.protolayout:protolayout-expression:1.1.0"

    // Use to preview wear tiles in your own app
    debugImplementation "androidx.wear.tiles:tiles-renderer:1.3.0"

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

Kotlin

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

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

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

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

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

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

카드 만들기

애플리케이션에서 카드를 제공하려면 다음 코드 샘플과 같이 TileService를 확장하는 클래스를 만들고 메서드를 구현합니다.

Kotlin

// Uses the ProtoLayout namespace for tile timeline objects.
// If you haven't done so already, migrate to the ProtoLayout namespace.
import androidx.wear.protolayout.TimelineBuilders.Timeline
import androidx.wear.protolayout.material.Text
import androidx.wear.tiles.TileBuilders.Tile

private val RESOURCES_VERSION = "1"
class MyTileService : TileService() {
    override fun onTileRequest(requestParams: RequestBuilders.TileRequest) =
        Futures.immediateFuture(Tile.Builder()
            .setResourcesVersion(RESOURCES_VERSION)
            .setTileTimeline(
                Timeline.fromLayoutElement(
                    Text.Builder(this, "Hello world!")
                        .setTypography(Typography.TYPOGRAPHY_DISPLAY1)
                        .setColor(argb(0xFF000000.toInt()))
                        .build()))
            .build())

    override fun onTileResourcesRequest(requestParams: ResourcesRequest) =
        Futures.immediateFuture(Resources.Builder()
            .setVersion(RESOURCES_VERSION)
            .build()
        )
}

Java

// Uses the ProtoLayout namespace for tile timeline objects.
// If you haven't done so already, migrate to the ProtoLayout namespace.
import androidx.wear.protolayout.TimelineBuilders.Timeline;
import androidx.wear.protolayout.material.Text;
import androidx.wear.tiles.TileBuilders.Tile;

public class MyTileService extends TileService {
    private static final String RESOURCES_VERSION = "1";

    @NonNull
    @Override
    protected ListenableFuture<Tile> onTileRequest(
        @NonNull TileRequest requestParams
    ) {
        return Futures.immediateFuture(new Tile.Builder()
            .setResourcesVersion(RESOURCES_VERSION)
            .setTileTimeline(
                Timeline.fromLayoutElement(
                    new Text.Builder(this, "Hello world!")
                        .setTypography(Typography.TYPOGRAPHY_DISPLAY1)
                        .setColor(ColorBuilders.argb(0xFF000000))
                        .build()))
            .build()
        );
   }

   @NonNull
   @Override
   protected ListenableFuture<Resources> onTileResourcesRequest(
       @NonNull ResourcesRequest requestParams
   ) {
       return Futures.immediateFuture(new Resources.Builder()
               .setVersion(RESOURCES_VERSION)
               .build()
       );
   }
}

다음으로, AndroidManifest.xml 파일의 <application> 태그 내부에 서비스를 추가합니다.

<service
   android:name=".MyTileService"
   android:label="@string/tile_label"
   android:description="@string/tile_description"
   android:icon="@drawable/tile_icon_round"
   android:roundIcon="@drawable/tile_icon_round"
   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>

   <meta-data android:name="androidx.wear.tiles.PREVIEW"
       android:resource="@drawable/tile_preview" />
</service>

권한과 인텐트 필터가 이 서비스를 카드 제공자로 등록합니다.

아이콘, 라벨, 설명은 사용자가 휴대전화나 시계에서 카드를 구성할 때 표시됩니다.

미리보기 메타데이터 태그를 사용하면 휴대전화에서 카드를 구성할 때 카드의 미리보기를 표시할 수 있습니다.

카드 서비스 수명 주기 개요

앱 매니페스트에서 TileService를 만들고 선언한 후에는 카드 서비스의 상태 변경에 응답할 수 있습니다.

TileService바인드된 서비스입니다. TileService는 앱 요청의 결과로 또는 시스템이 이 요청과 통신해야 하는 경우 바인드됩니다. 일반적인 바인드된 서비스 수명 주기에는 onCreate(), onBind(), onUnbind(), onDestroy() 등 4가지 콜백 메서드가 포함됩니다. 시스템은 서비스가 새로운 수명 주기 단계에 진입할 때마다 이러한 메서드를 호출합니다.

바인딩된 서비스 수명 주기를 제어하는 콜백 외에도 TileService 수명 주기와 관련된 다른 메서드를 구현할 수 있습니다. 모든 카드 서비스는 시스템의 업데이트 요청에 응답하기 위해 onTileRequest()onTileResourcesRequest()를 구현해야 합니다.

  • onTileAddEvent(): 사용자가 카드를 처음 추가할 때와 사용자가 카드를 다시 삭제하고 추가할 때만 시스템이 이 메서드를 호출합니다. 이때가 일회성 초기화를 수행하는 것이 가장 좋습니다.

    onTileAddEvent()는 시스템에서 카드를 생성할 때가 아니라 카드 집합이 재구성된 경우에만 호출됩니다. 예를 들어 기기가 재부팅되거나 전원이 켜지면 이미 추가된 카드에 관해 onTileAddEvent()가 호출되지 않습니다. 대신 getActiveTilesAsync()를 사용하여 자신에게 속한 타일의 활성 상태를 확인할 수 있습니다.

  • onTileRemoveEvent(): 사용자가 카드를 삭제하는 경우에만 시스템에서 이 메서드를 호출합니다.

  • onTileEnterEvent(): 이 제공자가 제공하는 타일이 화면에 표시될 때 시스템이 이 메서드를 호출합니다.

  • onTileLeaveEvent(): 이 제공자가 제공하는 타일이 화면에서 보이지 않으면 시스템이 이 메서드를 호출합니다.

  • onTileRequest(): 시스템이 이 제공자에 새 타임라인을 요청할 때 시스템이 이 메서드를 호출합니다.

  • onTileResourcesRequest(): 시스템이 이 제공업체에 리소스 번들을 요청할 때 시스템이 이 메서드를 호출합니다. 이는 카드가 처음 로드될 때 또는 리소스 버전이 변경될 때마다 발생할 수 있습니다.

활성 상태인 타일 쿼리

활성 타일은 시계에 표시하기 위해 추가된 타일입니다. TileService의 정적 메서드 getActiveTilesAsync()를 사용하여 앱에 속한 카드가 활성 상태인 카드를 쿼리합니다.

카드 UI 만들기

카드의 레이아웃은 빌더 패턴을 사용하여 작성됩니다. 카드의 레이아웃은 레이아웃 컨테이너와 기본 레이아웃 요소로 이루어진 일종의 트리처럼 구성됩니다. 각 레이아웃 요소에는 다양한 setter 메서드를 통해 설정할 수 있는 속성이 있습니다.

기본 레이아웃 요소

protolayout 라이브러리의 다음 시각적 요소는 Material 구성요소와 함께 지원됩니다.

  • Text: 텍스트 문자열을 렌더링합니다(선택적으로 줄바꿈 가능).
  • Image: 이미지를 렌더링합니다.
  • Spacer: 요소와 요소 사이에 패딩을 제공하거나 배경 색상을 설정할 때 구분선 역할을 할 수 있습니다.

Material 구성요소

protolayout-material 라이브러리는 기본 요소 외에도 Material Design 사용자 인터페이스 권장사항과 일치하는 카드를 디자인할 수 있도록 지원하는 구성요소를 제공합니다.

  • Button: 클릭 가능한 원형 구성요소로, 아이콘을 포함할 수 있습니다.
  • Chip: 클릭 가능한 경기장 모양의 구성요소로, 최대 두 줄의 텍스트와 선택적으로 아이콘을 포함할 수 있습니다.

  • CompactChip: 클릭 가능한 경기장 모양의 구성요소로, 한 줄의 텍스트를 포함할 수 있습니다.

  • TitleChip: 클릭 가능한 경기장 모양의 구성요소로, Chip과 비슷하지만 제목 텍스트를 수용할 수 있도록 높이가 더 큽니다.

  • CircularProgressIndicator: 원형 진행률 표시기로, 화면 가장자리 주위에 진행률을 표시하도록 EdgeContentLayout 내부에 배치할 수 있습니다.

레이아웃 컨테이너

Material 레이아웃과 함께 다음 컨테이너가 지원됩니다.

  • Row: 하위 요소를 가로로 나란히 배치합니다.
  • Column: 하위 요소를 세로로 차례로 배치합니다.
  • Box: 하위 요소를 다른 하위 요소 위에 오버레이합니다.
  • Arc: 하위 요소를 원에 배치합니다.
  • Spannable: 텍스트 섹션에 인터리브 처리 텍스트 및 이미지와 함께 특정 FontStyles를 적용합니다. 자세한 내용은 Spannable을 참고하세요.

모든 컨테이너는 하나 이상의 하위 요소를 포함할 수 있으며, 하위 요소 자체도 컨테이너가 될 수 있습니다. 예를 들어, Column은 여러 개의 Row 요소를 하위 요소로 포함하여 그리드와 같은 레이아웃이 될 수 있습니다.

하나의 컨테이너 레이아웃과 두 개의 하위 레이아웃 요소를 갖는 카드는 다음과 같습니다.

Kotlin

private fun myLayout(): LayoutElement =
    Row.Builder()
        .setWidth(wrap())
        .setHeight(expand())
        .setVerticalAlignment(VALIGN_BOTTOM)
        .addContent(Text.Builder()
            .setText("Hello world")
            .build()
        )
        .addContent(Image.Builder()
            .setResourceId("image_id")
            .setWidth(dp(24f))
            .setHeight(dp(24f))
            .build()
        ).build()

Java

private LayoutElement myLayout() {
    return new Row.Builder()
        .setWidth(wrap())
        .setHeight(expand())
        .setVerticalAlignment(VALIGN_BOTTOM)
        .addContent(new Text.Builder()
            .setText("Hello world")
            .build()
        )
        .addContent(new Image.Builder()
            .setResourceId("image_id")
            .setWidth(dp(24f))
            .setHeight(dp(24f))
            .build()
        ).build();
}

Material 레이아웃

protolayout-material 라이브러리는 기본 레이아웃 외에도 요소를 특정 '슬롯'에 배치하는 몇 가지 독자적인 레이아웃을 제공합니다.

  • PrimaryLayout: 단일 기본 작업 CompactChip을 하단에 배치하고 그 위 중앙에 콘텐츠를 배치합니다.

  • MultiSlotLayout: 기본 라벨 및 보조 라벨을 배치하고 그 사이에 선택적인 콘텐츠와 하단에 선택적인 CompactChip을 배치합니다.

  • MultiButtonLayout: Material 가이드라인에 따라 정렬된 버튼 집합을 배치합니다.

  • EdgeContentLayout: 콘텐츠를 CircularProgressIndicator와 같이 화면 가장자리 주변에 배치합니다. 이 레이아웃을 사용하면 레이아웃 내의 콘텐츠에 적절한 여백과 패딩이 자동으로 적용됩니다.

지원되는 Arc 컨테이너 하위 요소는 다음과 같습니다.

  • ArcLine: 호를 따라 곡선을 렌더링합니다.
  • ArcText: 호 안에서 곡선 텍스트를 렌더링합니다.
  • ArcAdapter: 호 안의 호에 접하는 지점에 그려지는 기본 레이아웃 요소를 렌더링합니다.

자세한 내용은 각 요소 유형에 관한 참고 문서를 확인하세요.

수정자

사용 가능한 모든 레이아웃 요소에는 선택적으로 수정자를 적용할 수 있습니다. 이러한 수정자의 용도는 다음과 같습니다.

  • 레이아웃의 시각적 모양을 변경합니다. 예를 들어, 레이아웃 요소에 배경, 테두리 또는 패딩을 추가합니다.
  • 레이아웃에 관한 메타데이터를 추가합니다. 예를 들어, 스크린 리더와 함께 사용할 수 있도록 시맨틱 수정자를 레이아웃 요소에 추가합니다.
  • 기능을 추가합니다. 예를 들어, 레이아웃 요소에 클릭 가능한 수정자를 추가하여 대화형 카드를 만듭니다. 자세한 내용은 카드와 상호작용을 참고하세요.

예를 들어, 다음 코드 샘플과 같이 Image의 기본 디자인과 메타데이터를 맞춤설정할 수 있습니다.

Kotlin

private fun myImage(): LayoutElement =
    Image.Builder()
        .setWidth(dp(24f))
        .setHeight(dp(24f))
        .setResourceId("image_id")
        .setModifiers(Modifiers.Builder()
            .setBackground(Background.Builder().setColor(argb(0xFFFF0000)).build())
            .setPadding(Padding.Builder().setStart(dp(12f)).build())
            .setSemantics(Semantics.builder()
                .setContentDescription("Image description")
                .build()
            ).build()
        ).build()

Java

private LayoutElement myImage() {
   return new Image.Builder()
           .setWidth(dp(24f))
           .setHeight(dp(24f))
           .setResourceId("image_id")
           .setModifiers(new Modifiers.Builder()
                   .setBackground(new Background.Builder().setColor(argb(0xFFFF0000)).build())
                   .setPadding(new Padding.Builder().setStart(dp(12f)).build())
                   .setSemantics(new Semantics.Builder()
                           .setContentDescription("Image description")
                           .build()
                   ).build()
           ).build();
}

Spannable

Spannable은 텍스트와 유사한 요소를 배치하는 특별한 유형의 컨테이너입니다. 이 기능은 텍스트의 큰 블록에서 하나의 하위 문자열에만 다른 스타일을 적용하려고 할 때 유용합니다. 이러한 작업은 Text 요소로는 할 수 없습니다.

Spannable 컨테이너는 Span 하위 요소로 채워집니다. 다른 하위 요소 또는 중첩된 Spannable 인스턴스는 허용되지 않습니다.

Span 하위 요소에는 두 가지 유형이 있습니다.

  • SpanText: 텍스트를 특정 스타일로 렌더링합니다.
  • SpanImage: 이미지를 텍스트와 함께 인라인으로 렌더링합니다.

예를 들어, 다음 코드 샘플과 같이 'Hello world' 카드에서 'world'를 기울임꼴로 표시하고 단어 사이에 이미지를 삽입할 수 있습니다.

Kotlin

private fun mySpannable(): LayoutElement =
    Spannable.Builder()
        .addSpan(SpanText.Builder()
            .setText("Hello ")
            .build()
        )
        .addSpan(SpanImage.Builder()
            .setWidth(dp(24f))
            .setHeight(dp(24f))
            .setResourceId("image_id")
            .build()
        )
        .addSpan(SpanText.Builder()
            .setText("world")
            .setFontStyle(FontStyle.Builder()
                .setItalic(true)
                .build())
            .build()
        ).build()

Java

private LayoutElement mySpannable() {
   return new Spannable.Builder()
        .addSpan(new SpanText.Builder()
            .setText("Hello ")
            .build()
        )
        .addSpan(new SpanImage.Builder()
            .setWidth(dp(24f))
            .setHeight(dp(24f))
            .setResourceId("image_id")
            .build()
        )
        .addSpan(new SpanText.Builder()
            .setText("world")
            .setFontStyle(newFontStyle.Builder()
                .setItalic(true)
                .build())
            .build()
        ).build();
}

리소스 사용

카드에서는 앱의 어떤 리소스에도 액세스하지 못합니다. 이는 Android 이미지 ID를 Image 레이아웃 요소에 전달해 확인할 수 없다는 의미입니다. 대신 onTileResourcesRequest() 메서드를 재정의하고 리소스를 직접 제공하세요.

onTileResourcesRequest() 메서드 내에 이미지를 제공하는 방법에는 두 가지가 있습니다.

Kotlin

override fun onTileResourcesRequest(
    requestParams: ResourcesRequest
) = Futures.immediateFuture(
Resources.Builder()
    .setVersion("1")
    .addIdToImageMapping("image_from_resource", ImageResource.Builder()
        .setAndroidResourceByResId(AndroidImageResourceByResId.Builder()
            .setResourceId(R.drawable.image_id)
            .build()
        ).build()
    )
    .addIdToImageMapping("image_inline", ImageResource.Builder()
        .setInlineResource(InlineImageResource.Builder()
            .setData(imageAsByteArray)
            .setWidthPx(48)
            .setHeightPx(48)
            .setFormat(ResourceBuilders.IMAGE_FORMAT_RGB_565)
            .build()
        ).build()
    ).build()
)

Java

@Override
protected ListenableFuture<Resources> onTileResourcesRequest(
       @NonNull ResourcesRequest requestParams
) {
return Futures.immediateFuture(
    new Resources.Builder()
        .setVersion("1")
        .addIdToImageMapping("image_from_resource", new ImageResource.Builder()
            .setAndroidResourceByResId(new AndroidImageResourceByResId.Builder()
                .setResourceId(R.drawable.image_id)
                .build()
            ).build()
        )
        .addIdToImageMapping("image_inline", new ImageResource.Builder()
            .setInlineResource(new InlineImageResource.Builder()
                .setData(imageAsByteArray)
                .setWidthPx(48)
                .setHeightPx(48)
                .setFormat(ResourceBuilders.IMAGE_FORMAT_RGB_565)
                .build()
            ).build()
        ).build()
);
}