タイルのスタートガイド


アプリでタイルの提供を開始するには、アプリの build.gradle ファイルに次の依存関係を追加します。

Groovy

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

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

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

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

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

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

Kotlin

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

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

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

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

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

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

主な概念

タイルは Android アプリと同じ方法で作成されず、次のような異なるコンセプトを使用します。

  • レイアウト テンプレート: ディスプレイ上の視覚要素の全体的な配置を定義します。これは primaryLayout() 関数によって行われます。
  • レイアウト要素: ボタンカードなどの個々のグラフィック要素、またはbuttonGroup などを使用してグループ化された複数の要素を表します。これらはレイアウト テンプレートに埋め込まれています。
  • リソース: ResourceBuilders.Resources オブジェクトは、レイアウトのレンダリングに必要な Android リソース(画像)の Key-Value ペアのマップバージョンで構成されています。
  • タイムライン: TimelineBuilders.Timeline オブジェクトは、レイアウト オブジェクトの 1 つ以上のインスタンスのリストです。レンダラがレイアウト オブジェクトを切り替えるタイミング(特定の時刻にレイアウトの表示を停止するなど)を示すために、さまざまなメカニズムと式を指定できます。
  • 状態: タイル間とアプリ間で渡される StateBuilders.State 型のデータ構造。これにより、2 つのコンポーネントが相互に通信できるようになります。たとえば、タイルでボタンがタップされた場合、その状態にはボタンの ID が保持されます。マップを使用してデータ型を交換することもできます。
  • タイル: タイルを表す TileBuilders.Tile オブジェクト。タイムラインリソース バージョン ID更新間隔状態で構成されます。
  • Protolayout: この用語は、さまざまなタイルに関連するクラスの名前に登場します。これは、さまざまな Wear OS サーフェスで使用されるグラフィック ライブラリである Wear OS Protolayout ライブラリを指します。

タイルを作成する

アプリからタイルを提供するには、TileService タイプのサービスを実装して、マニフェストに登録します。これに基づいて、システムは onTileRequest() の呼び出し時に必要なタイルをリクエストし、onTileResourcesRequest() の呼び出し時に必要なリソースをリクエストします。

class MyTileService : TileService() {

    override fun onTileRequest(requestParams: RequestBuilders.TileRequest) =
        Futures.immediateFuture(
            Tile.Builder()
                .setResourcesVersion(RESOURCES_VERSION)
                .setTileTimeline(
                    Timeline.fromLayoutElement(
                        materialScope(this, requestParams.deviceConfiguration) {
                            primaryLayout(
                                mainSlot = {
                                    text("Hello, World!".layoutString, typography = BODY_LARGE)
                                }
                            )
                        }
                    )
                )
                .build()
        )

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

次に、AndroidManifest.xml ファイルの <application> タグ内にサービスを追加します。

<service
    android:name=".snippets.m3.tile.MyTileService"
    android:label="@string/tile_label"
    android:description="@string/tile_description"
    android:icon="@mipmap/ic_launcher"
    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>

権限とインテント フィルタにより、このサービスがタイル プロバイダとして登録されます。

アイコン、ラベル、説明、プレビュー リソースは、ユーザーがスマートフォンまたはスマートウォッチでタイルを設定するときに表示されます。プレビュー リソースは Android のすべての標準リソース修飾子をサポートしているため、画面サイズやデバイスの言語などの要素に応じてプレビューを変更できます。その他の推奨事項については、プレビュー チェックリストをご覧ください。

アプリをデプロイし、タイルをタイルのカルーセルに追加します(タイルをプレビューするよりデベロッパー向けの方法もありますが、今は手動で行うだけです)。

「Hello World」タイル。
図 1. 「Hello World」タイル。

完全な例については、GitHub のコードサンプルまたはcodelab をご覧ください。

タイルの UI を作成する

マテリアル 3 の表現力豊かな UI 要素は、Kotlin のタイプセーフ ビルダー パターンを活用した構造化アプローチで作成されます。

レイアウト

レイアウトを作成するには、次の操作を行います。

  1. マテリアル デザイン スコープを開始する: 必要な contextdeviceConfiguration を指定して materialScope() 関数を呼び出します。allowDynamicThemedefaultColorScheme などのオプション パラメータを指定できます。allowDynamicTheme はデフォルトで true です。defaultColorScheme は、動的な色が使用できない場合(ユーザーが機能をオフにしている場合など)、またはデバイスでサポートされていない場合(allowDynamicThemefalse の場合など)に使用される ColorScheme を表します。

  2. スコープ内で UI を作成します。特定のタイルのレイアウトのすべての UI コンポーネントは、単一のトップレベルの materialScope() 呼び出しのラムダ内で定義する必要があります。primaryLayout()textEdgeButton() などのこれらのコンポーネント関数は、MaterialScope の拡張関数であり、このレシーバ スコープで呼び出された場合のみ使用できます。

    materialScope(
        context = context,
        deviceConfiguration = requestParams.deviceConfiguration, // requestParams is passed to onTileRequest
        defaultColorScheme = myFallbackColorScheme
    ) {
        // inside the MaterialScope, you can call functions like primaryLayout()
        primaryLayout(
            titleSlot = { text(text = "Title".layoutString) },
            mainSlot = { text(text = "Main Content".layoutString) },
            bottomSlot = { textEdgeButton(text = "Action".layoutString) }
        )
    }
    

スロット

M3 では、タイルのレイアウトに Compose を参考にしたアプローチが使用され、3 つの異なるスロットが使用されます。上から順に、次のとおりです。

  1. titleSlot(通常はメインのタイトルまたはヘッダー用)。
  2. mainSlot: コア コンテンツ。
  3. bottomSlot: アクションや補足情報によく使用されます。エッジボタンもここに表示されます。
titleSlot、mainSlot、bottomSlot を示すタイル レイアウト
図 2. titleSlot、mainSlot、bottomSlot

各スロットの内容は次のとおりです。

  • titleSlot(省略可): 通常は、text() によって生成された数語です。
  • mainSlot(必須): ボタン グループなどの構造に編成されたコンポーネント。これらのコンポーネントは、相互に再帰的に埋め込むこともできます。たとえば、列に行を含めることができます。
  • bottomSlot(省略可): 通常は、エッジをぴったりと追従するボタンまたはテキストラベルで塗りつぶされます。

タイルはスクロールできないため、長いコンテンツ リストのページング、スクロール、処理用のコンポーネントはありません。フォントサイズが大きくなると、または翻訳によってテキストが長くなると、コンテンツが見えなくなることがあるため、注意してください。

UI コンポーネント

protolayout-material3 ライブラリには、マテリアル 3 Expressive の仕様とユーザー インターフェースの推奨事項に従って設計された多数のコンポーネントが用意されています。

ボタン

  • textButton(): 短いテキスト コンテンツ用の単一スロットを持つボタン
  • iconButton(): アイコンを表す単一のスロットを持つボタン
  • avatarButton(): 丸いアバター ボタン。最大 3 つのスロットがあり、垂直方向に積み重ねられたラベルとセカンダリ ラベル、その横にある画像(アバター)を表すコンテンツを配置できます。
  • imageButton(): 追加のスロットではなく画像のみを提供するクリック可能な画像ボタン(背景として backgroundImage など)
  • compactButton(): アイコンとその横のテキストを表す水平方向に積み重ねられたコンテンツを取得するためのスロットを最大 2 つ提供するコンパクト ボタン
  • button(): 垂直方向に積み重ねられたラベルとセカンダリ ラベルを表すコンテンツと、その横にあるアイコンを取得するためのスロットを最大 3 つ提供する丸いボタン

エッジボタン

  • iconEdgeButton(): アイコンや同様の丸い小さなコンテンツを取得するための単一のスロットを提供するエッジボタン
  • textEdgeButton(): テキストや長くて幅の広いコンテンツを取得するための単一のスロットを備えたエッジボタン

カード

  • titleCard(): 1 ~ 3 つのスロット(通常はテキストベース)を提供するタイトルカード
  • appCard(): 最大 5 つのスロット(通常はテキストベース)を提供するアプリカード
  • textDataCard(): 最大 3 つのスロットを縦方向に積み重ねたデータカード(通常はテキストまたは数字ベース)
  • iconDataCard(): アイコン付きの最大 3 つの縦方向に積み重ねられたスロット(通常はテキストまたは数字ベース)を提供するデータカード
  • graphicDataCard(): 進行状況インジケーターなどのグラフィック データのスロットと、通常はテキストの説明用に最大 2 つの縦方向に積み重ねられたスロットを提供するグラフィック データカード

進行状況インジケーター

レイアウト要素のグループ化

  • buttonGroup(): 子を横方向に並べて配置するコンポーネント レイアウト
  • primaryLayout(): 推奨される M3 レイアウト スタイルを表す全画面レイアウト。レスポンシブで、要素の配置を処理し、推奨されるマージンとパディングが適用されます。

テーマ設定

マテリアル 3 Expressive では、カラーシステムは 29 の標準色ロールで定義され、プライマリ、セカンダリ、ターシャリ、エラー、サーフェス、アウトラインの 6 つのグループに編成されています。

マテリアル 3 表現豊かなカラーシステム
図 3. マテリアル 3 の表現豊かなカラーシステム。

ColorScheme は、これらの 29 個のロールそれぞれを対応する色にマッピングします。MaterialScope の一部であり、コンポーネントは MaterialScope 内に作成する必要があるため、コンポーネントはスキームから色を自動的に取得します。このアプローチにより、すべての UI 要素がマテリアル デザインの基準に自動的に準拠します。

ユーザーが、定義したカラーパターン(ブランドの色を反映したものなど)と、システムが提供するカラーパターン(ユーザーの現在のウォッチフェイスから派生したもの、またはユーザーが選択したもの)のどちらかを選択できるようにするには、次のように MaterialScope を初期化します。

val myColorScheme =
    ColorScheme(
        primary = ...
        onPrimary = ...
        // 27 more
    )

materialScope(
  defaultColorScheme = myColorScheme
) {
  // If the user selects "no theme" in settings, myColorScheme is used.
  // Otherwise, the system-provided theme is used.
}

指定したカラーパターンでタイルを強制的に表示するには、allowDynamicThemefalse に設定して、ダイナミック テーマ設定のサポートを無効にします。

materialScope(
  allowDynamicTheme = false,
  defaultColorScheme = myColorScheme
) {
  // myColorScheme is *always* used.
}

各個々のコンポーネントは、ColorScheme で定義された 29 個のカラーロールのサブセットを使用します。たとえば、ボタンは最大 4 色を使用します。デフォルトでは、アクティブな ColorScheme の「プライマリ」グループから取得されます。

ButtonColors コンポーネント トークン ColorScheme ロール
containerColor primary
iconColor onPrimary
labelColor onPrimary
secondaryLabelColor onPrimary(不透明度 0.8)

特定の UI 要素のデフォルトのカラートークンから変更する必要がある場合があります。たとえば、1 つの textEdgeButton で「プライマリ」ではなく「セカンダリ」または「ターシャリ」グループの色を使用して、目立たせてコントラストを高めることができます。

コンポーネントの色は、次の方法でカスタマイズできます。

  1. 事前定義された色にヘルパー関数を使用する。filledTonalButtonColors() などのヘルパー関数を使用して、マテリアル 3 Expressive の標準ボタン スタイルを適用します。これらの関数は、塗りつぶし、トーン、輪郭線などの一般的なスタイルを、MaterialScope 内のアクティブな ColorScheme の適切なロールにマッピングする、事前構成された ButtonColors インスタンスを作成します。これにより、一般的なボタンタイプごとに色を手動で定義しなくても、一貫したスタイルを適用できます。

    textEdgeButton(
        colors = filledButtonColors() // default
        /* OR colors = filledTonalButtonColors() */
        /* OR colors = filledVariantButtonColors() */
        // ... other parameters
    )
    

    カードの場合は、同等の filledCardColors() ファミリーの関数を使用します。

    1 つまたは 2 つのトークンのみを変更する必要がある場合は、ヘルパー関数によって返された ButtonColors オブジェクトを copy() メソッドを使用して変更することもできます。

    textEdgeButton(
        colors =
            filledButtonColors()
                .copy(
                    containerColor = colorScheme.tertiary,
                    labelColor = colorScheme.onTertiary
                )
        // ... other parameters
    )
    
  2. 交換用カラーロールを明示的に指定する。独自の ButtonColors オブジェクトを作成し、コンポーネントに渡します。カードの場合は、同等の CardColors オブジェクトを使用します。

    textEdgeButton(
        colors =
            ButtonColors(
                // the materialScope makes colorScheme available
                containerColor = colorScheme.secondary,
                iconColor = colorScheme.secondaryDim,
                labelColor = colorScheme.onSecondary,
                secondaryLabelColor = colorScheme.onSecondary
            )
        // ... other parameters
    )
    
  3. 固定色を指定する(慎重に使用)。一般に、意味論的な役割(colorScheme.primary)に直接色値を指定することもできます。特にテーマが動的に変更される場合は、テーマ全体との不整合につながる可能性があるため、このアプローチは控えめに使用してください。

    textEdgeButton(
        colors = filledButtonColors().copy(
            containerColor = android.graphics.Color.RED.argb, // Using named colors
            labelColor = 0xFFFFFF00.argb // Using a hex code for yellow
        )
        // ... other parameters
    )
    

タイポグラフィ

Wear OS プラットフォーム全体で視覚的な一貫性を保ち、パフォーマンスを最適化するため、タイルのすべてのテキストはシステム提供のフォントを使用してレンダリングされます。つまり、タイルではカスタム タイポフェイスはサポートされていません。Wear OS 6 以降では、これは OEM 固有のフォントです。ほとんどの場合、可変フォントであり、より表現力豊かなエクスペリエンスとよりきめ細かい制御を提供します。

通常、テキスト スタイルを作成するには、タイポグラフィ定数と組み合わせて text() メソッドを使用します。このコンポーネントを使用すると、マテリアル 3 Expressive の事前定義されたタイポグラフィ ロールを利用できます。これにより、タイルの読みやすさと階層に関する確立されたタイポグラフィのベスト プラクティスに準拠できます。このライブラリには、BODY_MEDIUM などの18 のセマンティック タイポグラフィ定数が用意されています。これらの定数は、サイズ以外のフォント軸にも影響します。

text(
    text = "Hello, World!".layoutString,
    typography = BODY_MEDIUM,
)

より詳細に制御するには、追加の設定を指定します。Wear OS 6 以降では、可変フォントが使用される可能性があり、斜体太さ丸みの軸に沿って変更できます。これらの軸は、settings パラメータを使用して制御できます。

text(
    text = "Hello, World".layoutString,
    italic = true,

    // Use elements defined in androidx.wear.protolayout.LayoutElementBuilders.FontSetting
    settings =
        listOf(weight(500), width(100F), roundness(100)),
)

最後に、サイズ文字間隔を制御する必要がある場合(推奨されません)、text() ではなく basicText() を使用し、fontStyle() を使用して fontStyle プロパティの値を作成します。

形状と余白

ほとんどのコンポーネントの角の半径は、shape プロパティを使用して変更できます。値は MaterialScope プロパティ shapes から取得されます。

textButton(
   height = expand(),
   width = expand(),
   shape = shapes.medium, // OR another value like shapes.full
   colors = filledVariantButtonColors(),
   labelContent = { text("Hello, World!".layoutString) },
)

コンポーネントの形状を変更した後、ディスプレイの端の周囲にスペースが多すぎるか少なすぎる場合は、primaryLayout()margin パラメータを使用して余白を調整します。

primaryLayout(
    mainSlot = {
        textButton(
            shape = shapes.small,
            /* ... */
        )
    },
    // margin constants defined in androidx.wear.protolayout.material3.PrimaryLayoutMargins
    margins = MAX_PRIMARY_LAYOUT_MARGIN,
)

円弧

サポートされている 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 は、テキストと同様に要素をレイアウトする特別なタイプのコンテナです。大きなテキスト ブロック内の 1 つの部分文字列だけに別のスタイルを適用する場合に便利です。これは Text 要素では不可能です。

Spannable コンテナは Span の子で満たされます。他の子やネストされた Spannable インスタンスは使用できません。

Span の子には、次の 2 種類があります。

  • 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() メソッド内で画像を指定する方法には次の 2 つがあります。

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()
);
}

タイルのプレビュー画像のチェックリスト

システムは、Android アプリ マニフェストで参照されているタイルのプレビュー画像をタイル カルーセル エディタに表示します。このエディタは、Wear OS デバイスとスマートフォンのスマートウォッチ コンパニオン アプリの両方に表示されます。

ユーザーがこのプレビュー画像を最大限に活用できるように、タイルの次の詳細を確認します。

  • 最新のデザインを反映。プレビューは、タイルの最新のデザインを正確に表す必要があります。
  • 静的なカラーテーマを使用します。ダイナミック カラーではなく、タイルの静的カラーテーマを使用します。
  • アプリアイコンを含む。プレビュー画像の上部にアプリのアイコンが表示されていることを確認します。
  • 読み込み済み/ログイン済みの状態を表示します。プレビューには、空のコンテンツやプレースホルダ コンテンツを表示せず、完全に機能する「読み込み済み」または「ログイン済み」の状態を表示する必要があります。
  • リソース解決ルールを活用してカスタマイズする(省略可)。Android のリソース解決ルールを使用して、デバイスのディスプレイ サイズ、言語、またはロケール設定に一致するプレビューを提供することを検討してください。これは、タイルの外観がデバイスによって異なる場合に特に便利です。