开始使用 Tile


要开始从您的应用提供 Tile,请将以下依赖项添加到您应用的 build.gradle 文件中。

Groovy

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

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

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

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

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

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

Kotlin

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

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

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

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

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

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

关键概念

Tile 的构建方式与 Android 应用不同,它们使用不同的概念。

  • 布局模板:定义显示屏上视觉元素的整体布局。这通过 primaryLayout() 函数实现。
  • 布局元素:表示单个图形元素,例如按钮卡片,或者使用按钮组或类似方法将多个此类元素组合在一起。这些元素嵌入在布局模板中。
  • 资源: ResourceBuilders.Resources 对象由一个映射组成,该映射包含渲染布局所需的 Android 资源(图片)的键值对,以及一个版本
  • 时间轴: TimelineBuilders.Timeline 对象是一个或多个布局对象实例的列表。您可以提供各种机制和表达式来指示渲染器何时从一个布局对象切换到另一个布局对象,例如在特定时间停止显示某个布局。
  • 状态: 类型为 StateBuilders.State 的数据结构,在 Tile 和应用之间传递,以使这两个组件能够相互通信。例如,如果 Tile 上的某个按钮被点按,则该状态会包含该按钮的 ID。您也可以使用映射来交换数据类型。
  • Tile:一个 TileBuilders.Tile 对象,表示一个 Tile,它由一个时间轴、一个资源版本 ID新鲜度间隔状态组成。
  • Protolayout:此术语出现在各种与 Tile 相关的类的名称中,指的是 Wear OS Protolayout 库,这是一个在各种 Wear OS 界面上使用的图形库。

创建 Tile

要从您的应用提供 Tile,请实现一个类型为 TileService 的服务,并将其注册到您的清单中。系统将根据此服务在调用 onTileRequest() 时请求必要的Tile,在调用 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>

权限和 intent 过滤器会将此服务注册为 Tile 提供程序。

当用户在手机或手表上配置 Tile 时,系统会向用户显示图标、标签、说明和预览资源。请注意,预览资源支持 Android 的所有标准资源限定符,因此可以根据屏幕尺寸和设备语言等因素更改预览。如需了解更多建议,请参阅预览清单

部署您的应用,然后将 Tile 添加到 Tile 轮播界面(也有一种更方便开发者预览 Tile 的方法,但目前请手动操作)。

'Hello World' Tile.
图 1. “Hello World”Tile。

如需查看完整示例,请参阅 GitHub 上的代码示例Codelab

为 Tile 创建界面

Material 3 Expressive 界面元素采用 Kotlin 的类型安全构建器模式支持的结构化方法创建。

布局

有关创建高效且自适应 Tile 布局的设计原则指导,请参阅Tile 的常见布局

要创建布局,请执行以下操作:

  1. 初始化 Material Design 作用域:调用 materialScope() 函数,提供所需的 contextdeviceConfiguration。您可以添加可选参数,例如 allowDynamicThemedefaultColorSchemeallowDynamicTheme 默认情况下为 true,而 defaultColorScheme 表示当动态颜色不可用(例如用户关闭该功能或设备不支持该功能)或 allowDynamicThemefalse 时使用的 ColorScheme)。

  2. 在作用域内构建界面:特定 Tile 布局的所有界面组件都必须在单个顶级 materialScope() 调用的 lambda 内定义。这些组件函数(例如 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 中,Tile 布局采用受 Compose 启发的方法,使用三个不同的插槽。从上到下,它们分别是:

  1. titleSlot,通常用于主标题或页眉。
  2. mainSlot,用于核心内容。
  3. bottomSlot,通常用于操作或补充信息。边缘按钮也显示在此处。
Tiles layout showing titleSlot, mainSlot and bottomSlot
图 2. titleSlot、mainSlot 和 bottomSlot。

每个插槽的内容如下:

  • titleSlot(可选):通常是 text() 生成的一些字词。
  • mainSlot(必填):组织成以下结构(例如按钮组)的组件。这些组件也可以递归地相互嵌入;例如,一个列可以包含多行。
  • bottomSlot(可选):通常填充有边缘按钮或文本标签。

由于 Tile 无法滚动,因此没有用于分页、滚动或处理长列表内容的组件。请注意,在字体大小增大或文本因翻译而变长时,内容仍应可见。

界面组件

protolayout-material3 库提供了大量根据 Material 3 Expressive 规范和用户界面建议设计的组件。

按钮

  • textButton():用于(短)文本内容的单个插槽按钮
  • iconButton():用于表示图标的单个插槽按钮
  • avatarButton():药丸形头像按钮,提供最多三个插槽,用于放置垂直堆叠的标签和次要标签,以及旁边的图片(头像)
  • imageButton():可点击的图片按钮,不提供额外的插槽,只提供图片(例如 backgroundImage 作为背景)
  • compactButton():紧凑型按钮,提供最多两个插槽,用于放置水平堆叠的内容,包括图标和旁边的文本
  • button():药丸形按钮,提供最多三个插槽,用于放置垂直堆叠的标签和次要标签,以及旁边的图标

边缘按钮

  • iconEdgeButton():边缘按钮,提供单个插槽,用于放置图标或类似圆形、小尺寸内容
  • textEdgeButton():边缘按钮,提供单个插槽,用于放置文本或类似长宽内容

卡片

  • titleCard():标题卡片,提供一到三个插槽,通常基于文本
  • appCard():应用卡片,提供最多五个插槽,通常基于文本
  • textDataCard():数据卡片,提供最多三个垂直堆叠的插槽,通常基于文本或数字
  • iconDataCard():数据卡片,提供最多三个垂直堆叠的插槽,通常基于文本或数字,并带有一个图标
  • graphicDataCard():图形数据卡片,提供一个图形数据插槽(例如进度指示器),以及最多两个垂直堆叠的插槽,通常用于文本描述

进度指示器

对布局元素进行分组

  • buttonGroup():组件布局,将其子项水平放置
  • primaryLayout():全屏布局,表示一种建议的 M3 布局样式,该样式具有响应性,可处理元素的放置,并应用了推荐的边距和内边距

主题设置

在 Material 3 Expressive 中,颜色系统由 29 个标准颜色角色定义,分为六组:primary(主色)、secondary(次色)、tertiary(三级色)、error(错误色)、surface(背景色)和 outline(轮廓色)。

Material 3 Expressive Color System
图 3. Material 3 Expressive 颜色系统。

一个 ColorScheme 会将这 29 个角色中的每一个映射到相应的颜色,并且由于它属于 MaterialScope 的一部分,组件必须在其内部创建,因此它们会自动从配色方案中获取颜色。这种方法使所有界面元素自动遵循 Material Design 标准。

为了让用户在您定义的配色方案(例如反映您品牌颜色的方案)和系统提供的配色方案(根据用户当前表盘派生,或由用户选择)之间进行选择,请按如下方式初始化 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.
}

要强制 Tile 以您提供的配色方案显示,请将 allowDynamicTheme 设置为 false 来停用对动态主题的支持。

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

颜色

每个单独的组件都使用 ColorScheme 定义的 29 个颜色角色中的一部分。例如,按钮最多使用四种颜色,这些颜色默认取自活动 ColorScheme 的“primary”组。

ButtonColors 组件令牌 ColorScheme 角色
containerColor primary
iconColor onPrimary
labelColor onPrimary
secondaryLabelColor onPrimary(不透明度 0.8)

有关将颜色应用于 Wear OS 设计的详细指导,请参阅颜色设计指南

您可能需要偏离特定界面元素的默认颜色令牌。例如,您可能希望某个 textEdgeButton 使用“secondary”或“tertiary”组中的颜色,而不是“primary”,以使其更突出并提供更好的对比度。

您可以通过以下几种方式自定义组件颜色:

  1. 使用辅助函数进行预定义颜色。使用辅助函数,例如 filledTonalButtonColors() 来应用 Material 3 Expressive 的标准按钮样式。这些函数会创建预配置的 ButtonColors 实例,这些实例会将填充、色调或轮廓等常见样式映射到 MaterialScope 中活动的 ColorScheme 中的相应角色。这可让您应用一致的样式,而无需手动为常见按钮类型定义每种颜色。

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

    对于卡片,请使用等效的 filledCardColors() 系列函数。

    您也可以使用辅助函数返回的 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 平台中创建视觉一致性并优化性能,Tile 上的所有文本都使用系统提供的字体进行渲染。也就是说,Tile 不支持自定义字体。在 Wear OS 6 及更高版本上,这是 OEM 专用的字体。在大多数情况下,它将是可变字体,可提供更具表现力的体验和更精细的控制。

要创建文本样式,您通常使用 text() 方法以及排版常量。此组件可让您利用 Material 3 Expressive 中预定义的排版角色,这有助于您的 Tile 遵循既定的排版最佳实践,以提高可读性和层级结构。该库提供了一组 18 个语义排版常量,例如 BODY_MEDIUM。这些常量还会影响字体轴,而不仅仅是大小。

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)),
)

最后,如果您需要控制大小字距(不推荐),请使用 basicText() 而不是 text(),并使用 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:在弧形中渲染一个基本布局元素,该元素与弧形相切绘制。

如需了解更多信息,请参阅每种元素类型的参考文档

修饰符

每个可用的布局元素都可以选择应用修饰符。使用这些修饰符的目的是:

  • 更改布局的视觉外观。例如,为布局元素添加背景、边框或内边距。
  • 添加有关布局的元数据。例如,为布局元素添加语义修饰符,以便与屏幕阅读器配合使用。
  • 添加功能。例如,为布局元素添加可点击修饰符,以使您的 Tile 具有交互性。如需了解更多信息,请参阅与 Tile 互动

例如,我们可以自定义 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”Tile 中将“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();
}

处理资源

Tile 无法访问您的应用中的任何资源。这意味着您无法将 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()
);
}

Tile 预览图片核对清单

系统会在 Tile 轮播编辑器中显示 Android 应用清单中引用的 Tile 预览图片,用于添加新的 Tile。此编辑器显示在 Wear OS 设备和手机上的手表配套应用中。

为帮助用户充分利用此预览图片,请验证您的 Tile 的以下详细信息:

  • 反映最新设计。预览应准确代表您的 Tile 的最新设计。
  • 使用建议尺寸。为提供最佳显示质量和良好的用户体验,预览图片尺寸应为 400 像素 x 400 像素。
  • 使用静态颜色主题。使用 Tile 的静态颜色主题,而不是动态主题。
  • 包含应用图标。确认您的应用图标显示在预览图片的顶部。
  • 显示加载/登录状态。预览应显示功能齐全的“已加载”或“已登录”状态,避免任何空内容或占位符内容。
  • 利用资源解析规则进行自定义(可选)。考虑使用 Android 的资源解析规则来提供与设备的显示尺寸、语言或区域设置相匹配的预览。如果您的 Tile 在不同设备上的外观有所不同,这会特别有用。