Save the date! Android Dev Summit is coming to Mountain View, CA on November 7-8, 2018.

Define layouts on Wear

Wear OS apps use the same layout techniques as handheld Android devices, but need to be designed with specific constraints. Do not port functionality and the UI from a handheld app and expect a good user experience.

For more information on designing great wearable apps, read the Wear OS design guidelines.

When you create layouts for Wear OS apps, you need to account for devices with square and round screens. Content near the corners of the screen may be cropped on round Wear OS devices. Therefore, layouts designed for square screens can have display issues on round devices.

For example, Figure 1 shows how the following layout looks on square and round screens:

Figure 1. Demonstration of how a layout designed for square screens does not work well on round screens.

Thus, using the following settings for your layout, the text doesn't display correctly on devices with round screens:

<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>

There are two approaches to this problem:

  1. Using layouts in the Wear UI Library for both square and round devices.
    • BoxInsetLayout - This layout applies different window insets depending on the shape of the device screen. Use this approach when you want to use a similar layout on both screen shapes without having views cropped near the edges of round screens.
    • Curved Layout - Use this layout when you want to display and manipulate a vertical list of items optimized for round screens.
  2. Providing alternative layout resources for square and round devices as described in the Providing Resources guide. At runtime, Wear detects the shape of the device screen and loads the correct layout.

To compile an Android Studio project with this library, ensure that the Extras > Google Repository package is installed in the Android SDK manager. Additionally, include the following dependencies in the build.gradle file of your wear module:

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

Use a BoxInsetLayout

Figure 2. Window insets on a round screen.

The BoxInsetLayout class in the Wear UI Library lets you define a single layout that works for both square and round screens. This class applies the required window insets depending on the screen shape and lets you easily align views on the center or near the edges of the screen.

Note: The BoxInsetLayout class replaces a similar, deprecated class in the Wearable Support Library.

The gray square in Figure 2 shows the area where BoxInsetLayout can automatically place its child views on round screens after applying the required window insets. To be displayed inside this area, child views specify the boxedEdges attribute with these values:

  • A combination of top, bottom, left, and right. For example, a "left|top" value positions the child's left and top edges inside the gray square in Figure 2.
  • The "all" value positions all the child's content inside the gray square in Figure 2.

On square screens, the window insets are zero and the boxedEdges attribute is ignored.

Figure 3. A layout definition that works on both square and round screens.

The layout shown in Figure 3 uses the <BoxInsetLayout> element and works on square and round screens:

<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>

Notice the parts of the layout marked in bold:

  • android:padding="15dp"

    This line assigns padding to the <BoxInsetLayout> element. The window insets on round devices are larger than 15dp, so this padding only applies to square screens.

  • android:padding="5dp"

    This line assigns padding to the inner FrameLayout element. This padding applies to both square and round screens. The total padding between the buttons and the window insets is 20 dp on square screens (15+5) and 5 dp on round screens.

  • app:boxedEdges="all"

    This line ensures that the FrameLayout element and its children are boxed inside the area defined by the window insets on round screens. This line has no effect on square screens.

Use a curved layout

The WearableRecyclerView class in the Wear UI Library lets you opt-in for a curved layout, optimized for round screens. To enable a curved layout for scrollable lists in your app, see Creating a Curved Layout.

Use different layouts for square and round screens

A Wear device can have a square or round screen. Your app needs to be able to support either device configuration. To do this, you should provide alternative resources. Apply the -round and -notround resource qualifiers to layouts, dimensions, or other resource types.

For example, consider organizing layouts as follows:

  • The layout/ directory contains layouts that work for both circular and square watches.
  • The layout-round/ and layout-notround/ directories contain layouts that are specific to the shape of a screen.

You can also use the res/values, res/values-round, and res/values-notround resource directories. By organizing resources in this way, you can share a single layout while changing only specific attributes, based on the device type.

Vary the values

An easy way to build for round and square watches is by using values/dimens.xml and values-round/dimens.xml. By specifying different padding settings, you can create the following layout with a single layout.xml file and the two dimens.xml files:

<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>
Using values-round/dimens.xml

Figure 4. Using 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>
Using values/dimens.xml

Figure 5. Using values/dimens.xml.

You should experiment with different values to see what works best.

Use XML to compensate for the chin

Some watches have an inset (also known as a “chin”) in an otherwise circular screen. If you don't compensate, some of your design may be obscured by the chin.

For example, you may have the following design:

Basic heart design

Figure 6. Basic heart design.

This activity_main.xml snippet defines its layout.

<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>

If you do nothing, part of the design will disappear into the chin.

Basic heart design

Figure 7. No fix applied.

You can use the fitsSystemWindows attribute to set the padding to avoid the chin. The following activity_main.xml snippet shows the use of fitsSystemWindows:

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

Figure 8. Using the fitsSystemWindows attribute.

Note that the top and bottom padding values that you defined are overridden to make everything fit in the system window. The way to fix this is to replace the padding values using an InsetDrawable.

Create an inset_favourite.xml file to define the padding values:

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

Remove the padding from the activity_main.xml:

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

Figure 9. Using an InsetDrawables.

Manage the chin programmatically

If you require more control than what is possible declaratively using XML, you can programmatically adjust your layout. To obtain the size of the chin you should attach a View.OnApplyWindowInsetsListener to the outermost view of your layout.

Add the following to MainActivity.java:

Kotlin

private var mChinSize: 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 ->
            mChinSize = insets.systemWindowInsetBottom
            // The following line is important for inner elements which react to insets
            v.onApplyWindowInsets(insets)
            insets
        }
    }
}

Java

private int mChinSize;
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) {
            mChinSize = insets.getSystemWindowInsetBottom();
            // The following line is important for inner elements which react to insets
            v.onApplyWindowInsets(insets);
            return insets;
        }
    });
}