3. 实现圆形屏幕自适应布局
圆形屏幕的“安全区”从哪来
Wear OS 圆形表盘的物理显示区域是一个圆,但系统为应用提供的窗口仍是一个正方形。如果直接把控件贴到窗口四角,在圆形表盘上就会被切掉。系统通过 WindowInsets 暴露一个关键值:圆形内切矩形(inset rect),即这个正方形窗口内能完整容纳的最大矩形。圆形表盘上它是一个居中的正方形,方形表盘上它等于整个窗口。
BoxInsetLayout 的作用就是读取这个 inset,并让子视图按规则避开被裁切的区域。它提供三种对齐方式:
boxedEdges="all":子视图整体缩进到内切矩形内,任何方向都不裁切,适合整块内容。boxedEdges="left|right":只在左右方向缩进,上下可延伸到圆边,适合横向铺满的条状元素。boxedEdges="top|bottom":只在上下方向缩进,适合纵向元素。
下面这张图对比了同一份布局在圆形与方形表盘上的 inset 差异,以及 boxedEdges 不同取值时子视图的实际落位。
用 BoxInsetLayout 包住根布局
把 BoxInsetLayout 作为 XML 根节点,内部放一个 ConstraintLayout 承载真正的界面。这样约束关系仍由 ConstraintLayout 管理,而圆形安全区由外层负责。
<androidx.wear.widget.BoxInsetLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp"
app:boxedEdges="all">
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="今日步数"
android:textColor="#FFFFFF"
android:textSize="14sp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<TextView
android:id="@+id/steps"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="8,420"
android:textColor="#4FC3F7"
android:textSize="34sp"
android:textStyle="bold"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.wear.widget.BoxInsetLayout>
关键点:app:boxedEdges 写在 BoxInsetLayout 的直接子视图上,而不是根节点上。上面的 ConstraintLayout 是直接子视图,所以它接收 inset 缩进;它内部的 TextView 再通过约束居中,无需各自处理圆形裁切。
让内容在两种表盘上都合理
只缩进还不够。圆形表盘上可用区域接近正方形,方形表盘上则更宽。若把内容固定成一行横排,方形表盘会显得空,圆形表盘又可能挤。推荐做法是:
- 用
ConstraintLayout的百分比约束(layout_constraintWidth_percent)让主内容宽度随屏幕自适应,而不是写死 dp。 - 对需要贴边的元素使用
boxedEdges="left|right",让它横向铺满内切矩形,纵向可延伸到圆边。 - 对整块卡片使用
boxedEdges="all",保证四角不被裁切。
下面给出一个可运行的最小 Activity,加载上面的布局并验证两种表盘:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val steps = findViewById<TextView>(R.id.steps)
steps.text = "8,420"
}
}
运行验证:分别启动圆形与方形 Wear OS 模拟器,观察 steps 文本是否完整可见、是否被圆边裁切。圆形表盘上文本应落在内切矩形内;方形表盘上应能利用更宽的横向空间。
如果发现圆形表盘上仍有裁切,先检查
boxedEdges 是否写在了 BoxInsetLayout 的直接子视图上。写在更深层或根节点上都不会生效。不要用固定
layout_margin 模拟圆形安全区。不同表盘尺寸的 inset 比例不同,硬编码边距在方形表盘上会浪费空间,在圆形表盘上可能仍被裁切。