在 Compose for Wear OS 中预览界面

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

由于 Wear OS 设备采用圆形显示屏,边角会剪裁内容,并且 TimeText 和 ScrollIndicator 等系统叠加层会沿屏幕边缘弯曲,因此专门为 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 设备预览注释将屏幕可组合项同时封装在 AppScaffold 和 ScreenScaffold 中。这会渲染圆形手表显示屏,并确保:

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

预览隔离的组件

预览单个组件(例如自定义 Card、Button 或状态条状标签)时,请省略 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 = 0xFF000000、showBackground = true) 和圆形手表设备尺寸:

注释 渲染的内容 何时使用
@WearPreviewSmallRound WearDevices.SMALL_ROUND上的 1 张预览图片(192x192dp)。 快速迭代最受限的圆形显示大小。
@WearPreviewLargeRound WearDevices.LARGE_ROUND(227x227dp)上显示 1 个预览。 检查大尺寸手表的布局密度和额外间距。
@WearPreviewDevices 2 个预览版:SMALL_ROUND 和 LARGE_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...
    // ...
}