定义 Wear 上的布局

Wear OS 应用采用的布局技巧与手持式 Android 设备应用相同,但在设计时需要遵循特定的约束条件。请勿移植手持式设备应用中的功能和界面,不要指望这样能带来良好的用户体验。

如需详细了解如何设计出色的穿戴式设备应用,请阅读 Wear OS 设计指南

为 Wear OS 应用创建布局时,您需要考虑设备屏幕是方形还是圆形。在圆形 Wear OS 设备上,屏幕角落附近的内容可能会被剪裁掉。因此,专为方形屏幕设计的布局在圆形设备上可能会出现显示问题。

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

图 1. 展示了专为方形屏幕设计的布局在圆形屏幕上显示效果不佳。

因此,对您的布局使用以下设置时,文本在具有圆形屏幕的设备上无法正确显示:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:id="@+id/text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/hello_square" />
    </LinearLayout>
    

解决此问题的方法有两种:

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

要使用此库编译 Android Studio 项目,请确保在 Android SDK 管理器中安装了 Extras > Google Repository 软件包。此外,还要在 wear 模块的 build.gradle 文件中添加以下依赖项:

    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        compile 'com.android.support:wear:26.0.0'
    }
    

使用 BoxInsetLayout

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

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

注意BoxInsetLayout 类取代了穿戴式设备支持库中的一个与其类似且已弃用的类。

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

  • topbottomleftright 的组合。例如,如果指定值为 "left|top",会将子视图的左边缘和上边缘放置在图 2 中的灰色方框内。
  • 如果指定值为 "all",会将子视图的所有内容放置在图 2 中的灰色方框内。

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

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

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

    <android.support.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">

        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:padding="5dp"
            app:boxedEdges="all">

            <TextView
                android:gravity="center"
                android:layout_height="wrap_content"
                android:layout_width="match_parent"
                android:text="@string/sometext"
                android:textColor="@color/black" />

            <ImageButton
                android:background="@null"
                android:layout_gravity="bottom|left"
                android:layout_height="50dp"
                android:layout_width="50dp"
                android:src="@drawable/ok" />

            <ImageButton
                android:background="@null"
                android:layout_gravity="bottom|right"
                android:layout_height="50dp"
                android:layout_width="50dp"
                android:src="@drawable/cancel" />
        </FrameLayout>
    </android.support.wear.widget.BoxInsetLayout>
    

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

  • android:padding="15dp"

    此行代码可向 <BoxInsetLayout> 元素分配内边距。圆形设备上的窗口边衬区大于 15dp,所以此内边距仅适用于方形屏幕。

  • android:padding="5dp"

    此行代码可向内部 FrameLayout 元素分配内边距。此内边距同时适用于方形屏幕和圆形屏幕。按钮和窗口边衬区之间的总内边距在方形屏幕上为 20dp (15+5),在圆形屏幕上为 5dp。

  • app:boxedEdges="all"

    此行代码可确保将 FrameLayout 元素及其子元素框在圆形屏幕上窗口边衬区定义的区域内。此行代码对方形屏幕不起作用。

使用曲线布局

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

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

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

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

  • 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
      ...>
      <android.support.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 附加到布局的最外层视图。

将以下代码添加到 MainActivity.java

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;
            }
        });
    }