处理不同的手表形状

Wear OS 上的叠加层采用与其他 Android 设备相同的布局技术,但在设计时需要遵循特定于手表的约束。

注意:请不要将具体功能和界面从移动应用移植到 Wear OS 上,不要指望这样能带来良好的用户体验。

如果您设计的应用用于方形手表,那么在圆形手表上,屏幕角落附近的内容可能会被剪裁掉。如果您使用的是可滚动的垂直列表,这就没什么问题,因为用户可以滚动屏幕,居中显示内容。但是,对于单屏,这样可能会导致糟糕的用户体验。

例如,图 1 展示了以下布局在方形屏幕和圆形屏幕的显示效果:

图 1. 展示专为方形屏幕设计的布局在圆形屏幕上可能无法正常显示的示例。

如果您为布局使用以下设置,文本在圆形屏幕的设备上会无法正确显示:

<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/text"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:text="@string/very_long_hello_world"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

可以通过两种方法解决此问题:

  • 对方形设备和圆形设备都使用 Wear OS 界面库中的布局。
    • BoxInsetLayout:此布局根据设备屏幕的形状应用不同的窗口边衬区。如果您要对这两种屏幕形状使用相似的布局而又不希望圆形屏幕边缘附近的视图被剪裁掉,则可以使用此方法。
    • 曲线布局:当您要显示和操作针对圆形屏幕而优化的垂直项目列表时,可使用此布局。
  • 按照提供备用资源指南中所述,为方形设备和圆形设备提供备用布局资源。在运行时,Wear OS 会检测设备屏幕的形状并加载正确的布局。

如需详细了解如何设计叠加层,请参阅 Wear OS 设计指南

使用 BoxInsetLayout

图 2.圆形屏幕上的窗口边衬区。

您可以使用 Wear OS 界面库中的 BoxInsetLayout 类定义同时适用于方形和圆形屏幕的布局。此类会根据屏幕形状应用所需的窗口边衬区,并可让您轻松地在屏幕中心或边缘附近对齐视图。

图 2 中的灰色方形区域显示了在应用所需的窗口边衬区后,BoxInsetLayout 可在圆形屏幕上自动放置其子视图的区域。要使子视图显示在此区域内,可通过以下值指定 layout_boxedEdges 属性:

  • topbottomleftright 的组合。例如,"left|top" 值可将子视图的左侧边缘和顶部边缘定位在图 2 中的灰色方形内。
  • "all" 值可确定图 2 灰色方形中所有子视图内容的位置。这是包含 ConstraintLayout 的最常用方法。

在方形屏幕上,窗口边衬区为零,并且会忽略 layout_boxedEdges 属性。

图 3. 同时适用于方形屏幕和圆形屏幕的布局定义。

图 3 中显示的布局使用了 <BoxInsetLayout> 元素,同时适用于方形屏幕和圆形屏幕:

<androidx.wear.widget.BoxInsetLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_height="match_parent"
    android:layout_width="match_parent"
    android:padding="15dp">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="5dp"
        app:layout_boxedEdges="all">

        <TextView
            android:layout_height="wrap_content"
            android:layout_width="match_parent"
            android:text="@string/sometext"
            android:textAlignment="center"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <ImageButton
            android:background="@android:color/transparent"
            android:layout_height="50dp"
            android:layout_width="50dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            android:src="@drawable/ok" />

        <ImageButton
            android:background="@android:color/transparent"
            android:layout_height="50dp"
            android:layout_width="50dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            android:src="@drawable/cancel" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.wear.widget.BoxInsetLayout>

请注意该布局中以粗体标记的部分:

  • android:padding="15dp"

    此行为 <BoxInsetLayout> 元素指定内边距。

  • android:padding="5dp"

    此行为内部 ConstraintLayout 元素指定内边距。

  • app:layout_boxedEdges="all"

    此行可确保 ConstraintLayout 元素及其子项位于圆形屏幕上的窗口边衬区所定义的区域内。此行对方形屏幕没有影响。

使用曲线布局

您可以通过 Wear OS 界面库中的 WearableRecyclerView 类选择使用针对圆形屏幕进行了优化的曲线布局。如需为应用中的可滚动列表启用曲线布局,请参阅在 Wear OS 上创建列表

对方形屏幕和圆形屏幕使用不同的布局

Wear OS 设备可能具有方形屏幕或圆形屏幕。您的应用需要能够支持任一设备配置。为此,您应提供备用资源。对于布局、尺寸或其他资源类型,将资源限定符设置为 roundnotround

例如,考虑按如下方式组织布局:

  • layout/ 目录包含同时适用于圆形和方形手表的布局。
  • layout-round/layout-notround/ 目录包含特定屏幕形状的专用布局。

您还可以使用 res/valuesres/values-roundres/values-notround 资源目录。以这种方式组织资源,您可以共享一个布局,仅需根据设备类型更改特定属性即可。

改变值

使用 values/dimens.xmlvalues-round/dimens.xml 可轻松为圆形手表和方形手表构建布局。通过指定不同的内边距设置,您可以使用单个 layout.xml 文件和两个 dimens.xml 文件创建以下布局:

<dimen name="header_start_padding">36dp</dimen>
<dimen name="header_end_padding">22dp</dimen>
<dimen name="list_start_padding">36dp</dimen>
<dimen name="list_end_padding">22dp</dimen>
使用 values-round/dimens.xml

图 4.使用 values-round/dimens.xml

<dimen name="header_start_padding">16dp</dimen>
<dimen name="header_end_padding">16dp</dimen>
<dimen name="list_start_padding">10dp</dimen>
<dimen name="list_end_padding">10dp</dimen>
使用 values/dimens.xml

图 5. 使用 values/dimens.xml

您应尝试使用不同的值,看看哪个值效果最佳。

使用 XML 填补下巴

某些手表的圆形屏幕上有边衬区(也称为“下巴”)。如果不填补下巴,部分设计可能会被它遮挡住。

例如,您可能有以下设计:

简单的心形设计

图 6. 简单的心形设计。

activity_main.xml 代码段定义了其布局。

<FrameLayout
  ...
  <androidx.wear.widget.RoundedDrawable
    android:id="@+id/androidbtn"
    android:src="@drawable/ic_android"
    .../>
   <ImageButton
    android:id="@+id/lovebtn"
    android:src="@drawable/ic_favourite"
    android:paddingTop="5dp"
    android:paddingBottom="5dp"
    android:layout_gravity="bottom"
    .../>
</FrameLayout>

如果不采取任何措施,部分设计会被下巴挡住。

简单的心形设计

图 7. 未加以修正。

您可以使用 fitsSystemWindows 属性设置内边距来避开下巴。以下 activity_main.xml 代码段展示了 fitsSystemWindows 的用法:

<ImageButton
  android:id="@+id/lovebtn"
  android:src="@drawable/ic_favourite"
  android:paddingTop="5dp"
  android:paddingBottom="5dp"
  android:fitsSystemWindows="true"
  .../>
使用 fitsSystemWindows

图 8. 使用 fitsSystemWindows 属性。

请注意,您定义的上下内边距值会被替换,以使所有内容都能容纳在系统窗口中。解决此问题的方法是使用 InsetDrawable 替换内边距值。

创建 inset_favourite.xml 文件来定义内边距值:

<inset
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:drawable="@drawable/ic_favourite"
  android:insetTop="5dp"
  android:insetBottom="5dp" />

activity_main.xml 中移除内边距:

<ImageButton
  android:id="@+id/lovebtn"
  android:src="@drawable/inset_favourite"
  android:paddingTop="5dp"
  android:paddingBottom="5dp"
  android:fitsSystemWindows="true"
  .../>
使用 InsetDrawables

图 9. 使用 InsetDrawables

以编程方式管理下巴

如果您需要获得比使用 XML 以声明方式定义布局时更多的控制权限,可以通过编程方式调整布局。如需获取下巴的尺寸,请将 View.OnApplyWindowInsetsListener 附加到布局的最外层视图。

请将以下代码添加到主 activity 文件中:

Kotlin

private var chinSize: Int = 0

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    // find the outermost element
    findViewById<View>(R.id.outer_container).apply {
        // attach a View.OnApplyWindowInsetsListener
        setOnApplyWindowInsetsListener { v, insets ->
            chinSize = insets.systemWindowInsetBottom
            // The following line is important for inner elements which react to insets
            v.onApplyWindowInsets(insets)
            insets
        }
    }
}

Java

private int chinSize;
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    // find the outermost element
    final View container = findViewById(R.id.outer_container);
    // attach a View.OnApplyWindowInsetsListener
    container.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
        @Override
        public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
            chinSize = insets.getSystemWindowInsetBottom();
            // The following line is important for inner elements which react to insets
            v.onApplyWindowInsets(insets);
            return insets;
        }
    });
}