功能块使用入门


如需开始在应用中提供功能块,请在应用的 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 资源(图片)的键值对映射版本组成。
  • 时间轴TimelineBuilders.Timeline 对象是 布局对象的一个或多个实例的列表。您可以提供各种机制和表达式,以指明渲染程序何时应从一个布局对象切换到另一个布局对象,例如在特定时间停止显示布局。
  • 状态:一种类型为 StateBuilders.State 的数据结构,在功能块和应用之间传递,以便这两个组件能够相互通信。例如,如果用户点按功能块上的按钮,状态将包含该按钮的 ID。您还可以使用映射交换数据类型。
  • 功能块:表示功能块的 TileBuilders.Tile 对象,由时间轴资源版本 ID新鲜度间隔状态组成。
  • Protolayout:此术语出现在各种与功能块相关的类的名称中,是指 Wear OS Protolayout 库,这是一个在各种 Wear OS 途径中使用的图形库。

创建功能块

如需在应用中提供功能块,请实现 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>

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

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

部署应用,然后将功能块添加到功能块轮播界面(还有更适合开发者的功能块预览方式,但目前请仅手动操作)。

“Hello World”功能块。
图 1. “Hello World”功能块。

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

为功能块创建界面

Material 3 富有表现力的界面元素是使用由 Kotlin 的类型安全的构建器模式提供支持的结构化方法创建的。

布局

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

  1. 启动 Material Design 作用域:调用 materialScope() 函数,提供所需的 contextdeviceConfiguration。您可以添加可选参数,例如 allowDynamicThemedefaultColorSchemeallowDynamicTheme 默认为 truedefaultColorScheme 表示在动态配色不可用(例如用户已关闭该功能)或设备不支持该功能或 allowDynamicThemefalse 时使用的 ColorScheme

  2. 在作用域内构建界面:给定功能块布局的所有界面组件都必须在单个顶级 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 中,功能块布局采用了 Compose 风格的方法,该方法使用三个不同的。从上到下依次为:

  1. titleSlot,通常用于主要标题或标题。
  2. mainSlot,用于核心内容。
  3. bottomSlot,通常用于操作或补充信息。边缘按钮也会显示在此处。
显示 titleSlot、mainSlot 和 bottomSlot 的功能块布局
图 2. titleSlot、mainSlot 和 bottomSlot。

每个槽的内容如下所示:

  • titleSlot(可选):通常是 text() 生成的几个字词。
  • mainSlot(必填):按按钮组等结构整理的组件。这些组件还可以递归嵌套在彼此内部;例如,列可以包含行。
  • bottomSlot(可选):通常填充边缘贴合按钮或文本标签。

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

界面组件

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

按钮

  • textButton():具有一个用于(短)文本内容的槽位的按钮
  • iconButton():具有一个用于表示图标的槽位的按钮
  • avatarButton():药丸形头像按钮,最多可提供三个槽,用于放置代表垂直堆叠标签和辅助标签的内容,以及旁边的图片(头像)
  • imageButton():可点击的图片按钮,不提供其他槽,仅提供图片(例如,backgroundImage 作为背景)
  • compactButton():紧凑按钮,最多提供两个槽位来接收水平堆叠的内容,表示图标及其旁边的文本
  • button():药丸形按钮,最多提供三个槽位来接收内容,表示垂直堆叠的标签和辅助标签,以及旁边的图标

边缘按钮

  • iconEdgeButton():边缘按钮,提供一个槽位来接收图标或类似的圆形小内容
  • textEdgeButton():边缘按钮,提供单个槽位来接收文本或类似的长宽内容

卡片

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

进度指示器

对布局元素进行分组

  • buttonGroup():一种组件布局,用于按水平序列放置其子级
  • primaryLayout():全屏布局,表示建议的 M3 布局样式,该样式具有自适应性,并负责处理元素的放置,以及应用建议的外边距和内边距

主题

在 Material 3 Expressive 中,颜色系统由 29 个标准颜色角色定义,分为六组:主色、副色、第三色、错误色、表面色和轮廓色。

Material 3 富有表现力的配色系统
图 3. Material 3 富有表现力的配色系统。

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.
}

如需强制功能块以您提供的配色方案显示,请将 allowDynamicTheme 设置为 false,以停用对动态主题的支持:

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

颜色

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

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

您可能需要为特定界面元素使用与默认颜色令牌不同的颜色。例如,您可能希望一个 textEdgeButton 使用“辅色”或“第三色”组(而不是“主色”组)中的颜色,以便脱颖而出并提供更好的对比度。

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

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

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

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

    如果您只需要更改一个或两个令牌,还可以使用辅助函数的 copy() 方法修改辅助函数返回的 ButtonColors 对象:

    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() 方法与排版常量结合使用。借助此组件,您可以使用 Material 3 Expressive 中的预定义排版角色,这有助于功能块遵循已建立的排版最佳实践,从而提高可读性和层次结构。该库提供了一组 18 个语义排版常量,例如 BODY_MEDIUM。这些常量还会影响字体轴(除了尺寸之外)。

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

如需进行更多控制,您可以提供其他设置。在 Wear OS 6 及更高版本中,系统可能会使用可变字体,您可以沿 italicweightwidthroundness 轴修改该字体。您可以使用 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:沿着弧形渲染基本布局元素,在弧线的切线方向上进行绘制。

如需了解详情,请参阅每种元素类型的参考文档

修饰符

对于每种可用的布局元素,您都可以选择对其应用修饰符。这些修饰符有以下用途:

  • 更改布局的视觉外观。例如,为布局元素添加背景、边框或内边距。
  • 添加有关布局的元数据。例如,为布局元素添加 semantics 修饰符,以便与屏幕阅读器结合使用。
  • 添加功能。例如,为布局元素添加 clickable 修饰符,使用户可与功能块互动。如需了解详情,请参阅与功能块互动

例如,我们可以自定义 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 就非常有用。

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

功能块预览图片核对清单

系统会在功能块轮播界面编辑器中显示 Android 应用清单中引用的功能块预览图片。此编辑器会显示在 Wear OS 设备上,也会显示在手机上的手表配套应用中。

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

  • 反映最新设计。预览应准确反映功能块的最新设计。
  • 使用静态颜色主题。使用功能块的静态配色主题,而不是动态配色主题。
  • 包含应用图标。确认应用的图标显示在预览图片的顶部。
  • 显示已加载/已登录状态。预览应显示完全正常的“已加载”或“已登录”状态,避免出现任何空白或占位符内容。
  • 利用资源解析规则进行自定义(可选)。 不妨考虑使用 Android 的资源解析规则来提供与设备的显示大小、语言或语言区域设置相匹配的预览。如果功能块的外观因设备而异,此功能尤为有用。