开始使用磁贴

要开始从您的应用提供磁贴,请在您的应用的 build.gradle 文件中包含以下依赖项。

Groovy

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

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

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

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

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

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

Kotlin

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

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

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

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

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

    // Use to fetch tiles from a tile provider in your tests
    testImplementation("androidx.wear.tiles:tiles-testing:1.4.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()。每次服务进入新的生命周期阶段时,系统都会调用这些方法。

除了控制绑定服务生命周期的回调之外,您还可以实现其他特定于 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,以及交错文本和图像。有关更多信息,请参阅 Spannables

每个容器都可以包含一个或多个子元素,这些子元素本身也可以是容器。例如,一个 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: 将单个主 action 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();
}

Spannables

Spannable 是一种特殊的容器类型,它以类似于文本的方式布局元素。当您想对较大文本块中的一个子字符串应用不同的样式时,这很有用,而使用 Text 元素是无法实现的。

Spannable 容器充满了 Span 子元素。不允许使用其他子元素或嵌套的 Spannable 实例。

有两种类型的 Span 子元素

例如,您可以在“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()
);
}