在磁贴中显示动态更新

从 Tiles 1.2 开始,您可以使用动态表达式流式传输平台数据更新。然后,您可以将这些更新与磁贴中的动画关联。您的应用每秒都会收到此值的更新。

使用动态表达式,您无需在内容更改时刷新整个磁贴。为了在磁贴中创建更具吸引力的体验,请为这些动态对象添加动画。

将动态表达式与数据源关联

androidx.wear.protolayoutandroidx.wear.protolayout.material 命名空间包含许多类,它们的字段接受动态表达式。一些示例包括:

要将动态表达式用作磁贴中元素的可能值,请使用元素的相应 *Prop 动态属性类型,并将数据源传递给动态属性类型的构建器类的 setDynamicValue() 方法。

磁贴支持以下动态属性类型:

当您使用的动态表达式影响物理尺寸(磁贴中除颜色以外的任何值)时,您还必须指定一组相关的约束,例如字符串格式。这些约束允许系统渲染器确定一个值在磁贴中可能占据的最大空间量。通常,您通过调用以 setLayoutConstraintsForDynamic* 开头的方法,在元素级别而不是动态表达式级别指定这些约束。

以下代码片段展示了如何使用 3 位数字显示心率更新,并将 -- 作为回退值:

Kotlin

import androidx.wear.protolayout.material.Text

public override fun onTileRequest(requestParams: RequestBuilders.TileRequest) =
    Futures.immediateFuture(Tile.Builder()
        .setResourcesVersion(RESOURCES_VERSION)
        .setFreshnessIntervalMillis(60 * 60 * 1000) // 60 minutes
        .setTileTimeline(Timeline.fromLayoutElement(
            Text.Builder(this,
                TypeBuilders.StringProp.Builder("--")
                    .setDynamicValue(PlatformHealthSources.heartRateBpm()
                        .format()
                        .concat(DynamicBuilders.DynamicString.constant(" bpm")))
                    .build(),
                StringLayoutConstraint.Builder("000")
                    .build()
                ).build()
            )
        ).build()
    )

Java

import androidx.wear.protolayout.material.Text;

@Override
protected ListenableFuture<Tile> onTileRequest(
       @NonNull TileRequest requestParams
) {
    return Futures.immediateFuture(new Tile.Builder()
        .setResourcesVersion(RESOURCES_VERSION)
        .setFreshnessIntervalMillis(60 * 60 * 1000) // 60 minutes
        .setTileTimeline(Timeline.fromLayoutElement(
            new Text.Builder(
                this,
                new TypeBuilders.StringProp.Builder("--")
                    .setDynamicValue(PlatformHealthSources.heartRateBpm()
                        .format()
                        .concat(DynamicBuilders.DynamicString.constant(" bpm")))
                    .build(),
                new StringLayoutConstraint.Builder("000")
                    .build()
                ).build())
        ).build()
    );
}

在单个磁贴中使用少量表达式

Wear OS 对单个磁贴可以拥有的表达式数量设有上限。如果磁贴包含的动态表达式总数过多,动态值将被忽略,系统将回退到您提供给相应动态属性类型的静态值。

您可以安全地将以下表达式集添加到磁贴中,因为总表达式数量不多。因此,磁贴行为正确:

Kotlin

val personHealthInfo = DynamicString.constant("This person has walked ")
    .concat(PlatformHealthSources.dailySteps()
        .div(1000)
        .format())
    .concat("thousands of steps and has a current heart rate ")
    .concat(PlatformHealthSources.heartRateBpm()
        .format())
    .concat(" beats per minute")

Java

DynamicString personHealthInfo =
    DynamicString.constant("This person has walked ")
        .concat(PlatformHealthSources.dailySteps()
            .div(1000)
            .format())
        .concat("thousands of steps and has a current heart rate ")
        .concat(PlatformHealthSources.heartRateBpm()
            .format())
        .concat(" beats per minute");

然而,这个磁贴可能包含过多的表达式:

Kotlin

// Note that this template is applied as many times as the loop iterates.
// The system doesn't reuse dynamic expressions.
val dynamicStringTemplate = PlatformHealthSources.dailySteps()
    .div(1000)
    .format()

for (person in people) {
  // SomeProperty
    .setDynamicValue(
        DynamicBuilders.DynamicString.constant("Steps for ")
            .concat(person)
            .concat(" are ")
            .concat(dynamicStringTemplate)
    )
}

Java

// Note that this template is applied as many times as the loop iterates.
// The system doesn't reuse dynamic expressions.
DynamicString dynamicStringTemplate =
    PlatformHealthSources.dailySteps()
        .div(1000)
        .format();

for (int i = 0; i < people.size(); i++) {
  // SomeProperty
    .setDynamicValue(
        DynamicBuilders.DynamicString.constant("Steps for ")
            .concat(people[i])
            .concat(" are ")
            .concat(dynamicStringTemplate)
    );
}

将动态数据整合到状态对象中

您可以将数据源的最新更新集整合到一个“状态”中,然后将其传递给磁贴进行值渲染。

要在磁贴中使用状态信息,请完成以下步骤:

  1. 建立一组表示磁贴状态不同值的键。此示例创建了用于表示饮水量和备注的键:

    Kotlin

    companion object {
        val KEY_WATER_INTAKE = AppDataKey<DynamicInt32>("water_intake")
        val KEY_NOTE = AppDataKey<DynamicString>("note")
    }

    Java

    private static final AppDataKey<DynamicInt32> KEY_WATER_INTAKE =
        new AppDataKey<DynamicInt32>("water_intake");
    private static final AppDataKey<DynamicString> KEY_NOTE =
        new AppDataKey<DynamicString>("note");
  2. 在您的 onTileRequest() 实现中,调用 setState() 并建立从每个键到特定动态数据值的初始映射:

    Kotlin

    override fun onTileRequest(requestParams: TileRequest):
            ListenableFuture<Tile> {
        val state = State.Builder()
            .addKeyToValueMapping(KEY_WATER_INTAKE,
                DynamicDataBuilders.DynamicDataValue.fromInt(200))
            .addKeyToValueMapping(KEY_NOTE,
                DynamicDataBuilders.DynamicDataValue.fromString("Note about day"))
        .build()
        // ...
    
        return Futures.immediateFuture(Tile.Builder()
            // Set resources, timeline, and other tile properties.
            .setState(state)
            .build()
        )

    Java

    @Override
    protected ListenableFuture<Tile> onTileRequest(
                ListenableFuture<Tile> {
        State state = new State.Builder()
            .addKeyToValueMapping(KEY_WATER_INTAKE,
                DynamicDataBuilders.DynamicDataValue.fromInt(200))
            .addKeyToValueMapping(KEY_NOTE,
                DynamicDataBuilders.DynamicDataValue.fromString("Note about day"))
        .build();
        // ...
    
        return Futures.immediateFuture(Tile.Builder()
            // Set resources, timeline, and other tile properties.
            .setState(state)
            .build()
        );
    }
  3. 创建布局时,在您想要显示来自状态的数据的地方,使用 Dynamic* 类型对象。您还可以调用 animate() 来显示从前一个值到当前值的动画:

    Kotlin

    DynamicInt32.from(KEY_WATER_INTAKE).animate()

    Java

    DynamicInt32.from(KEY_WATER_INTAKE).animate();
  4. 需要时,您也可以使用新值更新状态。这可以是磁贴 LoadAction 的一部分。

    在此示例中,饮水量值更新为 400

    Kotlin

    val loadAction = LoadAction.Builder()
        .setRequestState(
            State.Builder()
                .addKeyToValueMapping(
                    KEY_WATER_INTAKE,
                    DynamicDataBuilders.DynamicDataValue.fromInt(400)
                )
                .build()
        )
        .build()

    Java

    LoadAction loadAction = new LoadAction.Builder()
        .setRequestState(
            new State.Builder()
                .addKeyToValueMapping(
                    KEY_WATER_INTAKE,
                    DynamicDataBuilders.DynamicDataValue.fromInt(400)
                ).build()
        ).build();