Compose for Wear OS 的列表功能


在 Wear OS 设备上,列表用于提供一组选项来供用户选择。

许多 Wear OS 设备都使用圆形屏幕,用户不方便查看靠近屏幕顶部和底部的列表项。因此,Compose for Wear OS 提供了一个名为 TransformingLazyColumnLazyColumn 类版本,该版本支持缩放和形变动画。当有列表项移至屏幕边缘时,便会缩小并淡出。

如需应用推荐的缩放和滚动效果,请执行以下操作:

  1. 使用 Modifier.transformedHeight,以便 Compose 在列表项在屏幕中滚动时计算高度变化。
  2. 使用 transformation = SurfaceTransformation(transformationSpec) 应用视觉效果,包括缩小列表项内容。
  3. 对于不将 transformation作为参数的组件(例如Text),请使用自定义TransformationSpec

以下动画展示了列表元素在靠近屏幕顶部和底部时如何缩放和改变形状:

以下代码段展示了如何使用 TransformingLazyColumn 布局来创建在各种大小的 Wear OS 屏幕上 看起来都很棒的内容

该代码段还演示了如何使用 minimumVerticalContentPadding 修饰符,您应在列表项中设置该修饰符,以便在列表顶部和底部应用正确的内边距。

如需显示滚动指示器,请在 ScreenScaffoldTransformingLazyColumn 之间共享 columnState

val columnState = rememberTransformingLazyColumnState()
val transformationSpec = rememberTransformationSpec()
ScreenScaffold(
    scrollState = columnState
) { contentPadding ->
    TransformingLazyColumn(
        state = columnState,
        contentPadding = contentPadding
    ) {
        item {
            ListHeader(
                modifier = Modifier
                    .fillMaxWidth()
                    .transformedHeight(this, transformationSpec)
                    .minimumVerticalContentPadding(ListHeaderDefaults.minimumTopListContentPadding),
                transformation = SurfaceTransformation(transformationSpec)
            ) {
                Text(text = "Header")
            }
        }
        // ... other items
        item {
            Button(
                modifier = Modifier
                    .fillMaxWidth()
                    .transformedHeight(this, transformationSpec)
                    .minimumVerticalContentPadding(ButtonDefaults.minimumVerticalListContentPadding),
                transformation = SurfaceTransformation(transformationSpec),
                onClick = { /* ... */ },
                icon = {
                    Icon(
                        imageVector = Icons.Default.Build,
                        contentDescription = "build",
                    )
                },
            ) {
                Text(
                    text = "Build",
                    maxLines = 1,
                    overflow = TextOverflow.Ellipsis,
                )
            }
        }
    }
}

添加贴靠和快速滑动效果

贴靠功能可确保用户完成滚动或快速滑动操作后,列表会停留在某个列表项上,该列表项会精确地位于特定位置(通常是屏幕中心)。在圆形屏幕上,列表项会随着远离中心而缩放和变形,因此贴靠功能特别有用,可确保最相关的列表项在最佳观看区域内保持完全可见和可读。

如需添加贴靠和快速滑动行为,请将 flingBehavior 参数设置为 TransformingLazyColumnDefaults.snapFlingBehavior(columnState)。 将 rotaryScrollableBehavior 设置为匹配,以便在使用实体表冠或表圈时获得一致的体验。为此,请使用 RotaryScrollableDefaults.snapBehavior(columnState)

val columnState = rememberTransformingLazyColumnState()
ScreenScaffold(scrollState = columnState) { contentPadding ->
    TransformingLazyColumn(
        state = columnState,
        flingBehavior = TransformingLazyColumnDefaults.snapFlingBehavior(columnState),
        rotaryScrollableBehavior = RotaryScrollableDefaults.snapBehavior(columnState)
    ) {
        // ...
        // ...
    }
}

反向布局

默认情况下,可滚动列表会锚定到其顶部边缘。如果用户已滚动到标准列表的底部,并且在末尾添加了新列表项,则列表会保持用户对当前列表项的视图。例如,如果用户正在查看屏幕底部的列表项 10,并且添加了列表项 11,则视图仍会聚焦于列表项 10,而列表项 11 会显示在当前视图下方的屏幕外。

对于消息应用或实时日志等用例,通常不希望出现此行为。当有新列表项到达时,如果用户已位于列表底部,通常希望立即看到最新内容。如果一次到达许多列表项,列表应跳过以显示底部的最新列表项(这意味着某些中间列表项可能根本不会显示,除非用户向上滚动)。

为了支持这些用例,TransformingLazyColumn 允许您通过设置 reverseLayout = true 来反转布局。这会将列表的锚点从顶部边缘更改为底部边缘。

为方便起见,设置 reverseLayout = true 还会反转列表项的视觉顺序和滚动操作的方向:

  • 列表项从底部到顶部进行组合,这意味着索引 0 显示在屏幕底部。
  • 向上滚动会显示索引较高的列表项。

如需添加贴靠和快速滑动行为以及反向布局,您可以将 flingBehaviorrotaryScrollableBehavior 结合使用,如以下代码段所示:

val columnState = rememberTransformingLazyColumnState()
val transformationSpec = rememberTransformationSpec()
ScreenScaffold(scrollState = columnState) { contentPadding ->
    TransformingLazyColumn(
        state = columnState,
        contentPadding = contentPadding,
        reverseLayout = true,
        modifier = Modifier.fillMaxWidth()
    ) {
        items(10) { index ->
            Button(
                label = {
                    Text(
                        text = "Item ${index + 1}"
                    )
                },
                onClick = {},
                modifier = Modifier
                    .fillMaxWidth()
                    .transformedHeight(this, transformationSpec)
                    .minimumVerticalContentPadding(ButtonDefaults.minimumVerticalListContentPadding),
                transformation = SurfaceTransformation(transformationSpec)
            )
        }
        item {
            // With reverseLayout = true, the last item declared appears at the top.
            ListHeader(
                modifier = Modifier
                    .fillMaxWidth()
                    .transformedHeight(this, transformationSpec)
                    .minimumVerticalContentPadding(ListHeaderDefaults.minimumTopListContentPadding),
                transformation = SurfaceTransformation(transformationSpec)
            ) {
                Text("Header")
            }
        }
    }
}

以下图片展示了普通列表和反向列表之间的区别:

具有正常布局的 TransformingLazyColumn,显示了顶部的“Item 1”和按升序排列的项。
图 1.标准列表布局,内容从上到下填充。
一个采用反向布局的 TransformingLazyColumn,其中“项目 1”位于底部,项目按降序排列,越往上越小。
图 2.反向列表布局,内容从下到上填充。

列表中的边缘按钮

对于 Material 3,您可以添加一个 EdgeButton,这是一个位于列表底部的边缘贴靠按钮。不过,请注意不要将其作为 TransformingLazyColumn 中的列表项添加,而是使用 ScreenScaffold 中的 edgeButton 插槽。

使用 edgeButton 插槽可确保按钮正确放置在屏幕底部,并在列表滚动时正常运行。

以下代码段展示了如何实现 EdgeButton

val columnState = rememberTransformingLazyColumnState()
ScreenScaffold(
    scrollState = columnState,
    edgeButton = {
        EdgeButton(
            onClick = { /* TODO */ },
            modifier = Modifier.scrollable(
                columnState,
                orientation = Orientation.Vertical,
                reverseDirection = true,
                // Apply overscroll to the EdgeButton for proper scrolling behavior.
                overscrollEffect = rememberOverscrollEffect(),
            )
        ) {
            Text("More")
        }
    }
) { contentPadding ->
    TransformingLazyColumn(
        contentPadding = contentPadding,
        state = columnState,
    ) {
        // ...
        // ...
    }
}

列表中的 SwipeToReveal

借助 SwipeToReveal 组件,您可以通过滑动来访问列表项(例如 CardChip)的操作。滑动通常会从侧面显示一个或两个操作按钮(例如“删除”或“更多”)。

TransformingLazyColumn 中使用 SwipeToReveal 时,请遵循以下准则:

  • 滚动时重置:当用户滚动列表时,将任何滑动打开的 列表项重置为其覆盖状态。
  • 高度一致:将操作按钮的高度设置为与内部 滑动列表项(无论是 Button 还是 Card)的高度一致,以确保外观 一致。
  • 转换容器:将 transformedHeight 修饰符和 transformationSpec 应用于 SwipeToReveal 组件本身。
  • 请勿进行双重转换:请勿将 transformedHeighttransformation 修饰符应用于内部滑动列表项( SwipeToReveal容器内的卡片或按钮)。