3. 实现圆形屏幕自适应布局

实现圆形屏幕自适应布局 学习卡片

圆形屏幕的“安全区”从哪来

Wear OS 圆形表盘的物理显示区域是一个圆,但系统为应用提供的窗口仍是一个正方形。如果直接把控件贴到窗口四角,在圆形表盘上就会被切掉。系统通过 WindowInsets 暴露一个关键值:圆形内切矩形(inset rect),即这个正方形窗口内能完整容纳的最大矩形。圆形表盘上它是一个居中的正方形,方形表盘上它等于整个窗口。

BoxInsetLayout 的作用就是读取这个 inset,并让子视图按规则避开被裁切的区域。它提供三种对齐方式:

  • boxedEdges="all":子视图整体缩进到内切矩形内,任何方向都不裁切,适合整块内容。
  • boxedEdges="left|right":只在左右方向缩进,上下可延伸到圆边,适合横向铺满的条状元素。
  • boxedEdges="top|bottom":只在上下方向缩进,适合纵向元素。

下面这张图对比了同一份布局在圆形与方形表盘上的 inset 差异,以及 boxedEdges 不同取值时子视图的实际落位。

圆形表盘 内切矩形 inset ≈ 12% 边长 四角被裁切,需缩进 方形表盘 内切矩形 = 窗口 inset = 0 可充分利用四角空间 boxedEdges 落位 all 四向缩进 left|right 仅左右缩进 top|bottom

用 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 比例不同,硬编码边距在方形表盘上会浪费空间,在圆形表盘上可能仍被裁切。