Wear OS アプリはハンドヘルドの Android デバイスと同じレイアウト手法を使用していますが、特定の制約を考慮してデザインする必要があります。ハンドヘルド アプリの機能や UI を移植しても、優れた操作性は期待できません。
優れたウェアラブル アプリをデザインする方法について詳しくは、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>
この問題に対するアプローチとして、次の 2 つがあります。
- 正方形と円形の両方のデバイスに Wear UI ライブラリのレイアウトを使用します。
- BoxInsetLayout - このレイアウトでは、デバイスの画面の形状に応じてさまざまなウィンドウ インセットを適用します。このアプローチは、どちらの画面形状でもほぼ同じレイアウトを使用しつつ、円形の画面の角付近のビューが切り取られないようにしたい場合に使用します。
- 曲線レイアウト - このレイアウトは、円形の画面向けに最適化された縦方向のアイテムリストを表示および操作したい場合に使用します。
- リソースの指定ガイドで説明されているように、正方形と円形のデバイス用に代替レイアウト リソースを指定します。Wear は実行時に、デバイスの画面の形状を検出して正しいレイアウトをロードします。
このライブラリを使用して Android Studio プロジェクトをコンパイルするには、[Extras] で Google Repository パッケージが Android SDK Manager にインストールされていることを確認します。また、wear
モジュールの build.gradle
ファイルに以下の依存関係を含めます。
dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:wear:26.0.0' }
BoxInsetLayout を使用する

図 2. 円形の画面上のウィンドウ インセット
Wear UI ライブラリの BoxInsetLayout
クラスを使用すると、正方形と円形の両方の画面で機能する単一のレイアウトを定義できます。このクラスでは、画面の形状に応じて必要なウィンドウ インセットを適用し、画面の中央または端付近のビューを容易に調整できます。
注: BoxInsetLayout
クラスは、ウェアラブル サポート ライブラリのサポートが終了した同種のクラスの後継クラスです。
図 2 のグレーの正方形は、BoxInsetLayout
が必要なウィンドウ インセットを適用した後に、円形の画面上に子ビューを自動的に配置できる領域を示しています。この領域内に表示されるようにするには、子ビューで以下の値が設定された boxedEdges
属性を指定します。
top
、bottom
、left
、right
の組み合わせ。たとえば、値"left|top"
は、子の左端と上端を図 2 のグレーの正方形内に配置します。- 値
"all"
は、子のすべてのコンテンツを図 2 のグレーの正方形内に配置します。
正方形の画面ではウィンドウ インセットがゼロのため、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"> <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> </androidx.wear.widget.BoxInsetLayout>
レイアウトのうち、太字でマークした部分に注目してみましょう。
-
android:padding="15dp"
この行では、
<BoxInsetLayout>
要素にパディングを割り当てています。円形のデバイスのウィンドウ インセットは 15 dp より大きいため、このパディングは正方形の画面にのみ適用されます。 -
android:padding="5dp"
この行では、内部の
FrameLayout
要素にパディングを割り当てています。このパディングは正方形と円形の両方の画面に適用されます。ボタンとウィンドウ インセット間のパディングの合計は、正方形の画面では 20 dp(15+5)、円形の画面では 5 dp です。 -
app:boxedEdges="all"
この行により、円形の画面のウィンドウ インセットによって定義されている領域内で
FrameLayout
要素とその子がボックス化されます。この行による正方形の画面への影響はありません。
曲線レイアウトを使用する
Wear UI ライブラリの
WearableRecyclerView
クラスを使用すると、円形の画面向けに最適化された曲線レイアウトを有効にすることができます。アプリでスクロール可能なリストの曲線レイアウトを有効にする場合は、曲線レイアウトの作成をご覧ください。
正方形と円形の画面で異なるレイアウトを使用する
Wear デバイスには正方形と円形の画面があります。アプリはどちらのデバイス構成にも対応できる必要があります。そのためには、代替リソースを指定する必要があり、-round
および -notround
リソース修飾子をレイアウト、ディメンション、その他のリソースタイプに適用します。
たとえば、次のようにレイアウトを整理することを検討します。
-
layout/
ディレクトリに、円形と正方形の両方のスマートウォッチで機能するレイアウトを格納します。 -
layout-round/
とlayout-notround/
の各ディレクトリに、画面の形状に固有のレイアウトを格納します。
また、res/values
、res/values-round
、res/values-notround
の各リソース ディレクトリを使用することも可能です。このようにリソースを整理することで、1 つのレイアウトを共有し、デバイスのタイプに基づいて特定の属性のみを変更できます。
値を変更する
values/dimens.xml
と values-round/dimens.xml
を使用すると、円形と正方形のスマートウォッチ用のレイアウトを簡単に作成できます。別々のパディング設定を指定すると、1 つの layout.xml
ファイルと 2 つの 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" .../>

図 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" .../>

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