在 Compose for Wear OS 中预览界面

借助 Android Studio Compose 预览功能,您可以在 IDE 中直接检查和验证 Wear OS 可组合项在不同手表显示屏尺寸、圆形边框和字体缩放比例下的效果,而无需将应用部署到实体手表或模拟器。

由于 Wear OS 设备采用圆形显示屏,边角会剪裁内容,并且 TimeTextScrollIndicator 等系统叠加层会沿屏幕边缘弯曲,因此专门为 Wear OS 配置预览对于尽早发现布局问题至关重要。


设置预览版依赖项

如需使用 Wear OS Compose 预览注释和设备定义,请将以下依赖项添加到模块的 build.gradle.kts 文件中:

dependencies {
    // Provides @WearPreview* multipreview annotations
    // (such as @WearPreviewDevices and @WearPreviewFontScales)
    implementation("androidx.wear.compose:compose-ui-tooling:1.7.0")

    // Provides WearDevices constants
    // (such as WearDevices.SMALL_ROUND and WearDevices.LARGE_ROUND)
    implementation("androidx.wear:wear-tooling-preview:1.0.0")

    // Standard Compose preview support and interactive/animation inspection
    implementation("androidx.compose.ui:ui-tooling-preview")
    debugImplementation("androidx.compose.ui:ui-tooling")
}

选择要预览的内容:界面还是组件

预览的配置方式取决于您是预览全屏还是隔离的界面组件

预览全屏 (AppScaffold + ScreenScaffold)

预览整个屏幕时,请务必使用 Wear 设备预览注释将屏幕可组合项同时封装在 AppScaffoldScreenScaffold 中。这会渲染圆形手表显示屏,并确保:

  • TimeText 渲染在表盘的顶部曲面边缘。
  • ScrollIndicator 会显示在右侧边框上。
  • EdgeButton 已正确定位,并在底部曲线处被剪裁。
  • 内容边衬区和圆形屏幕剪裁可准确反映真实的观看硬件。
@WearPreviewDevices
@Composable
fun WorkoutScreenPreview() {
    MaterialTheme {
        // AppScaffold provides the top-level TimeText overlay
        AppScaffold {
            // WorkoutScreen contains its own ScreenScaffold and content
            WorkoutScreen(
                heartRate = 142,
                elapsedTime = "12:45"
            )
        }
    }
}
在 WearDevices.SMALL_ROUND 上渲染的 WorkoutScreenPreview

小圆 (192x192dp)

在 WearDevices.LARGE_ROUND 上渲染的 WorkoutScreenPreview

大圆 (227x227dp)

预览隔离的组件

预览单个组件(例如自定义 CardButton 或状态条状标签)时,请省略 device 参数,并使用具有深色背景的标准 @Preview。这可确保 Wear Material 3 颜色和对比度准确显示,而无需渲染完整的圆形手表显示屏:

@Preview(
    showBackground = true,
    backgroundColor = 0xFF000000
)
@Composable
fun HeartRateCardPreview() {
    MaterialTheme {
        HeartRateCard(bpm = 142, zone = "Aerobic")
    }
}
HeartRateCardPreview 隔离组件预览,不含手表边框

隔离的组件预览(无设备框架)。


内置 Multipreview 注解

androidx.wear.compose.ui.tooling.preview 软件包提供内置注释,可自动配置深色背景 (backgroundColor = 0xFF000000showBackground = true) 和圆形手表设备尺寸:

注释 渲染的内容 何时使用
@WearPreviewSmallRound WearDevices.SMALL_ROUND上的 1 张预览图片(192x192dp)。 快速迭代最受限的圆形显示大小。
@WearPreviewLargeRound WearDevices.LARGE_ROUND(227x227dp)上显示 1 个预览。 检查大尺寸手表的布局密度和额外间距。
@WearPreviewDevices 2 个预览版SMALL_ROUNDLARGE_ROUND 针对每个屏幕可组合函数的标准多设备检查。
@WearPreviewFontScales SMALL_ROUND的 6 个预览,涵盖所有 Wear 字体缩放比例:小 (0.94f)、正常 (1.0f)、中 (1.06f)、大 (1.12f)、较大 (1.18f) 和最大 (1.24f)。 检查文本换行、省略号和按钮高度扩展。

您可以将 @WearPreviewDevices@WearPreviewFontScales 堆叠在同一预览函数上,以生成全面的测试矩阵:

@WearPreviewDevices
@WearPreviewFontScales
@Composable
fun MessageDetailScreenPreview() {
    MaterialTheme {
        AppScaffold {
            MessageDetailScreen(
                sender = "Alex",
                body = "Running 5 mins late!"
            )
        }
    }
}

自定义预览注释和硬件规格

如果您需要更精细的控制(例如测试特定的硬件尺寸、较长的本地化字符串或最糟糕的组合),可以直接配置 @Preview,也可以定义自己的自定义多预览注释。

可用的 WearDevices 常量和自定义硬件规格

androidx.wear.tooling.preview.devices.WearDevices 对象提供标准设备 ID:

  • WearDevices.SMALL_ROUND"id:wearos_small_round",192x192dp)
  • WearDevices.LARGE_ROUND"id:wearos_large_round",227x227dp)

如需在超大圆形显示屏(例如 44 毫米至 45 毫米的手表或 240x240dp 的 Ultra 型号)上进行预览,请将自定义 spec: 字符串传递给 device 形参:

@Preview(
    name = "XL Round Watch (240dp)",
    device = "spec:width=240dp,height=240dp,dpi=320,isRound=true",
    showBackground = true,
    backgroundColor = 0xFF000000
)
@Composable
fun WorkoutScreenXlPreview() {
    MaterialTheme {
        AppScaffold {
            WorkoutScreen(heartRate = 142, elapsedTime = "12:45")
        }
    }
}

创建自定义多预览注释

如需检查极端情况,请创建自定义多预览注释,将最小的圆形屏幕最大的字体缩放比例和详细的语言区域(例如德语)配对,同时搭配标准的圆形大屏幕:

@Preview(
    name = "1. Standard Large Round",
    group = "Layout extremes",
    device = WearDevices.LARGE_ROUND,
    backgroundColor = 0xFF000000,
    showBackground = true
)
@Preview(
    name = "2. Extreme Small Round (Largest Font + German)",
    group = "Layout extremes",
    device = WearDevices.SMALL_ROUND,
    fontScale = 1.24f,
    locale = "de-rDE",
    backgroundColor = 0xFF000000,
    showBackground = true
)
annotation class WearPreviewExtremes
标准大圆形预览

1. 标准大圆

极小圆形,字体缩放比例为最大

2. 极小圆形(最大字号 + 德语)


预览滚动列 (TransformingLazyColumn)

默认情况下,TransformingLazyColumn 会初始化为第一个项 (index = 0) 位于屏幕顶部。不过,在 Wear OS 上,当列表项靠近屏幕顶部和底部的曲面边缘时,它们的高度和圆角 (SurfaceTransformation) 会发生变化,并且只有在滚动到底部时才会显示 EdgeButton

如需预览列表在部分向下滚动或滚动到底部时的外观,请执行以下操作:

第 1 步:在屏幕可组合项中提升 TransformingLazyColumnState

允许屏幕可组合项接受以 rememberTransformingLazyColumnState() 为默认值的 TransformingLazyColumnState 形参:

@Composable
fun InboxScreen(
    messages: List<Message>,
    columnState: TransformingLazyColumnState = rememberTransformingLazyColumnState(),
) {
    val transformationSpec = rememberTransformationSpec()

    ScreenScaffold(
        scrollState = columnState,
        edgeButton = {
            EdgeButton(onClick = { /* Compose new */ }) {
                Text("New message")
            }
        }
    ) { contentPadding ->
        TransformingLazyColumn(
            state = columnState,
            contentPadding = contentPadding,
        ) {
            items(messages.size) { index ->
                Card(
                    onClick = {},
                    modifier = Modifier
                        .fillMaxWidth()
                        .transformedHeight(this, transformationSpec)
                        .minimumVerticalContentPadding(
                            CardDefaults.minimumVerticalListContentPadding
                        ),
                    transformation = SurfaceTransformation(transformationSpec),
                ) {
                    Text(messages[index].subject)
                }
            }
        }
    }
}

第 2 步:在 @Preview 中传递 initialAnchorItemIndex

rememberTransformingLazyColumnState 接受两个可选的初始滚动参数:

  • initialAnchorItemIndex: Int:如果设置为非负指数(例如 3),列表会初始化为以该项为中心显示在手表视口中
  • initialAnchorItemScrollOffset: Int:相对于居中锚定项应用的可选像素偏移量。

您可以创建并排预览,以显示同一屏幕的顶部中间(滚动)底部(EdgeButton 可见)状态:

@WearPreviewLargeRound
@Composable
fun InboxScreenTopPreview() {
    MaterialTheme {
        AppScaffold {
            // Default (-1): Pinned to top of list (index 0)
            InboxScreen(messages = sampleMessages)
        }
    }
}

@WearPreviewLargeRound
@Composable
fun InboxScreenScrolledMiddlePreview() {
    MaterialTheme {
        AppScaffold {
            // Centers item index 3 in the viewport, showing top/bottom item morphing
            InboxScreen(
                messages = sampleMessages,
                columnState = rememberTransformingLazyColumnState(
                    initialAnchorItemIndex = 3
                )
            )
        }
    }
}

@WearPreviewLargeRound
@Composable
fun InboxScreenBottomEdgeButtonPreview() {
    MaterialTheme {
        AppScaffold {
            // Anchors on the last item so the EdgeButton is visible at the bottom
            InboxScreen(
                messages = sampleMessages,
                columnState = rememberTransformingLazyColumnState(
                    initialAnchorItemIndex = sampleMessages.lastIndex
                )
            )
        }
    }
}
已将 InboxScreen 固定到列表顶部

顶部(默认值 -1

InboxScreen 滚动到中间索引 3

中 (initialAnchorItemIndex = 3)

InboxScreen 滚动到底部,EdgeButton 已展开

底部(EdgeButton 展开)

提示:您还可以点击 Android Studio 中任意 @Preview 上的开始互动模式,通过鼠标或触控板滚动 TransformingLazyColumn 直播,并实时检查 SurfaceTransformation 变形、EdgeButton 入场动画和 ScrollIndicator 移动。

在滚动捕获 (LocalScrollCaptureInProgress) 期间保护 ScrollIndicator

当系统滚动捕获(长屏幕截图)或多帧屏幕截图测试工具捕获滚动 TransformingLazyColumn 时,Compose 会在垂直捕获和拼接多个视口图块时将 LocalScrollCaptureInProgress.current 设置为 true

由于 ScreenScaffold 在滚动捕获期间不会自动隐藏其 scrollIndicator,因此除非您使用 !LocalScrollCaptureInProgress.current 明确保护浮动滚动条叠加层,否则该叠加层会在长屏幕截图的每个拼接图块上重复显示:

ScreenScaffold(
    scrollState = columnState,
    scrollIndicator = {
        if (!LocalScrollCaptureInProgress.current) {
            ScrollIndicator(state = columnState)
        }
    }
) { contentPadding ->
    // TransformingLazyColumn content...
    // ...
}