タイルのスタートガイド

アプリでタイルの提供を開始するには、アプリの 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(): このメソッドが呼び出されるのは、ユーザーが初めてタイルを追加した場合と、ユーザーがタイルを削除してから再度追加した場合のみです。これは、1 回限りの初期化を行うのに最適なタイミングです。

    onTileAddEvent() は、システムによってタイルが作成されるたびにではなく、一連のタイルが再構成された場合にのみ呼び出されます。たとえば、デバイスが再起動されたときや電源がオンになった場合、すでに追加されているタイルに対して onTileAddEvent() は呼び出されません。代わりに getActiveTilesAsync() を使用すると、アクティブなタイルのスナップショットを取得できます。

  • onTileRemoveEvent(): ユーザーがタイルを削除した場合にのみ、このメソッドが呼び出されます。

  • onTileEnterEvent(): プロバイダが提供するタイルが画面上に表示されると、システムがこのメソッドを呼び出します。

  • onTileLeaveEvent(): プロバイダが提供するタイルが画面のビュー外に出ると、システムがこのメソッドを呼び出します。

  • onTileRequest(): システムがこのプロバイダに新しいタイムラインをリクエストすると、システムがこのメソッドを呼び出します。

  • onTileResourcesRequest(): システムがこのプロバイダにリソース バンドルをリクエストすると、システムがこのメソッドを呼び出します。これは、タイルの初回読み込み時や、リソース バージョンの変更時に発生する可能性があります。

アクティブなタイルをクエリする

アクティブなタイルとは、スマートウォッチに表示するために追加されたタイルのことです。TileService の静的メソッド getActiveTilesAsync() を使用して、アプリに属するタイルをクエリします。

タイルの UI を作成する

タイルのレイアウトは、ビルダー パターンを使用して記述し、レイアウト コンテナと基本的なレイアウト要素からなるツリーのように作成します。各レイアウト要素にはプロパティがあり、さまざまなセッター メソッドで設定できます。

基本的なレイアウト要素

protolayout ライブラリの次の視覚的要素とマテリアル コンポーネントがサポートされています。

  • Text: テキスト文字列をレンダリングし、必要に応じて折り返します。
  • Image: 画像をレンダリングします。
  • Spacer: 要素間にパディングを設定します。背景色を設定するときは区切りとして機能します。

マテリアル コンポーネント

基本要素に加えて、protolayout-material ライブラリにはマテリアル デザイン ユーザー インターフェースの推奨事項に沿ったタイルデザインを実現するためのコンポーネントが用意されています。

  • Button: アイコンを含むようにデザインされた、クリック可能な円形のコンポーネント。
  • Chip: 最大 2 行のテキストと任意のアイコンを含むようにデザインされた、クリック可能なスタジアム形のコンポーネント。

  • CompactChip: 1 行のテキストを含むようにデザインされた、クリック可能なスタジアム形のコンポーネント。

  • TitleChip: Chip に似ているが、タイトル テキストを表示できるように高さが大きくなっている、クリック可能なスタジアム形のコンポーネント。

  • CircularProgressIndicator: EdgeContentLayout 内に配置して画面の端に進行状況を表示できる、円形の進行状況インジケーター。

レイアウト コンテナ

次のコンテナとマテリアル レイアウトがサポートされています。

  • Row: 子要素を水平方向に並べます。
  • Column: 子要素を垂直方向に並べます。
  • Box: 子要素を重ね合わせます。
  • Arc: 子要素を環状に並べます。
  • Spannable: テキストのセクションに特定の FontStyles を適用し、テキストや画像の間に入れます。詳細については、Spannable をご覧ください。

どのコンテナにも、子を 1 つ以上含めることができます。子はコンテナにすることもできます。たとえば、Column に子として複数の Row 要素を含めることができます。この場合、格子状のレイアウトになります。

一例として、コンテナ レイアウトと 2 つの子レイアウト要素を持つタイルは次のようになります。

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

マテリアル レイアウト

基本的なレイアウトに加えて、protolayout-material ライブラリには、特定の「スロット」内に要素を保持するための独自のレイアウトがいくつか用意されています。

  • PrimaryLayout: 1 つのプライマリ アクション CompactChip を下部に配置し、その中央にコンテンツを重ねます。

  • MultiSlotLayout: プライマリ ラベルとセカンダリ ラベルを配置し、その間にオプションのコンテンツを配置して、下部にオプションの CompactChip を配置します。

  • MultiButtonLayout: マテリアル ガイドラインに沿って並んだボタンのセットを配置します。

  • 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 は、テキストと同様に要素をレイアウトする特別なタイプのコンテナです。大きなテキスト ブロック内の 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()
);
}