3. Wear OS界面设计基础
Wear OS 的界面设计与手机端最大的差异,来自屏幕形态:绝大多数设备是圆形表盘,可用区域是一个内切于方形的圆。如果直接把手机上的布局搬到手表上,最常见的后果是文字被裁掉、按钮贴边、内容被系统表冠或底部手势区遮挡。Wear Compose 提供了一套面向手表的组件与布局约定,本节围绕 Scaffold、Text、Button 三个最基础的构件,讲清楚在圆形屏幕上如何把界面搭起来并保证可读、可点。
一、先理解圆形屏幕的“安全区”
Wear Compose 的布局坐标系与常规 Compose 一致:原点在左上角,x 向右为正,y 向下为正,单位是 dp。区别在于,圆形屏幕的四个角是无效区域。系统通过 Scaffold 暴露的 contentPadding(内边距)告诉我们哪些区域是安全的。这个内边距不是固定值,它会随设备形状(圆形/方形)、是否有系统指示器而变化。
因此有一条基本纪律:所有内容都应放在 Scaffold 提供的内边距之内,不要用硬编码的 padding 去“猜”圆形边界。 一个常见的错误是给根布局写死 padding(16.dp),在圆形表盘上,靠近顶部和底部的文字仍可能被裁切,因为圆形在垂直方向的有效宽度远小于水平方向。
二、Scaffold:手表界面的骨架
Scaffold 是 Wear Compose 中承载页面结构的容器,它负责处理屏幕形状带来的内边距,并预留时间、位置等系统指示器的位置。一个最小可用的手表页面结构如下:
@Composable
fun SimpleWatchScreen() {
Scaffold(
timeText = { TimeText() }, // 顶部时间,系统自动避让
vignette = { Vignette(vignettePosition = VignettePosition.Top) }
) { contentPadding ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(contentPadding), // 关键:使用系统给的安全内边距
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(text = "今日步数")
Text(text = "8,420", style = MaterialTheme.typography.displayMedium)
}
}
}
这里有两个要点。第一,timeText 让系统在顶部绘制时间,Scaffold 会自动把内容往下推,避免与时间重叠。第二,contentPadding 必须真正应用到内容容器上,否则安全区形同虚设。如果页面需要滚动,应把 contentPadding 传给 ScalingLazyColumn 的 contentPadding 参数,而不是套在外层 padding,否则滚动时内容会被裁切。
三、Text:圆形屏幕上的排版约束
圆形屏幕的可用宽度随垂直位置变化:越靠近顶部和底部,可用宽度越窄。因此文字排版要遵循“短、居中、少行”的原则。Wear Compose 的 Text 与标准 Compose 用法一致,但需要主动约束宽度与行数:
Text(
text = "今日步数",
style = MaterialTheme.typography.titleMedium,
textAlign = TextAlign.Center,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.fillMaxWidth()
)
建议正文使用 bodyMedium 及以上字号,手表上小于 12sp 的文字在强光下几乎不可读。数值类信息可用 displayMedium 突出,但同样要限制 maxLines。若一段说明必须超过两行,应改用滚动列表或拆分为多个页面,而不是压缩字号。
四、Button:可点击区域与圆形适配
手表上的点击目标比手机更苛刻,因为手指遮挡面积大。Wear Compose 推荐使用 Button 或 CompactButton,并保证最小点击尺寸不小于 48dp。在圆形屏幕上,按钮通常放在屏幕中央或底部中央,避免放在左右两侧的圆弧边缘。
Button(
onClick = { /* 处理点击 */ },
modifier = Modifier
.fillMaxWidth(0.8f) // 留出两侧圆弧余量
.height(48.dp)
) {
Text(text = "开始记录")
}
把按钮宽度设为屏幕宽度的 80% 左右,是一个实用的经验值:既保证点击面积,又不会顶到圆形边缘。若页面同时有多个按钮,应使用 Column 垂直排列并保持足够间距,不要横向并排,因为圆形屏幕横向空间在上下两端急剧收窄。
五、把三者组合成一个可运行页面
下面给出一个完整的示例:顶部显示时间,中间显示步数,底部一个按钮。示例参数(步数 8420、按钮文案“开始记录”)为教学假设值,实际运行时由数据源提供。
@Composable
fun StepScreen(onStart: () -> Unit) {
Scaffold(timeText = { TimeText() }) { padding ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(padding)
.padding(horizontal = 12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = "今日步数",
style = MaterialTheme.typography.titleMedium,
textAlign = TextAlign.Center,
maxLines = 1
)
Spacer(Modifier.height(4.dp))
Text(
text = "8,420",
style = MaterialTheme.typography.displayMedium,
textAlign = TextAlign.Center,
maxLines = 1
)
Spacer(Modifier.height(16.dp))
Button(
onClick = onStart,
modifier = Modifier.fillMaxWidth(0.8f).height(48.dp)
) {
Text("开始记录", maxLines = 1)
}
}
}
}
运行后应观察三点:时间是否与内容重叠、步数数字是否完整显示、按钮是否可稳定点击。若在圆形预览中数字被裁切,优先减小字号或缩短文案,而不是继续缩小 padding。若按钮点击不灵敏,检查高度是否达到 48dp、是否被其他元素覆盖。
六、常见问题与排查
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| 顶部文字被时间遮挡 | 未使用 Scaffold 的 timeText 或未应用 contentPadding | 启用 timeText,并把 contentPadding 应用到内容容器 |
| 文字在圆形边缘被裁切 | 内容超出安全区,或硬编码 padding | 改用 contentPadding,缩短文案,限制 maxLines |
| 按钮点不中 | 高度不足 48dp 或被遮挡 | 设置 height(48.dp),检查层级顺序 |
| 滚动列表内容被裁 | contentPadding 套在外层而非传给列表 | 把 contentPadding 传给 ScalingLazyColumn 的 contentPadding 参数 |
掌握 Scaffold 的安全区、Text 的排版约束和 Button 的点击尺寸,就具备了在圆形表盘上搭建可用界面的基础能力。后续更复杂的列表、卡片与导航,都建立在这三个构件的正确使用之上。