ConstraintLayout でレスポンシブ UI を作成する   Android Jetpack の一部

Compose の方法を試す
Jetpack Compose は、Android に推奨される UI ツールキットです。Compose でレイアウトを扱う方法について説明します。

ConstraintLayout を使用すると、ネストされたビューグループのない、フラットなビュー階層を持つ大規模で複雑なレイアウトを作成できます。すべてのビューが兄弟ビューや親のレイアウトとの関係に従って配置される点で、これは RelativeLayout と似ていますが、RelativeLayout よりも柔軟性があり、Android Studio の Layout Editor で使用しやすくなっています。

Layout API と Layout Editor は互いに利用するように作られているので、ConstraintLayout のすべての機能は Layout Editor のビジュアル ツールから直接利用できます。XML を編集するのではなく、ドラッグだけで ConstraintLayout を使用してレイアウトを作成できます。

このページでは、Android Studio 3.0 以降で ConstraintLayout を使用してレイアウトを作成する方法について説明します。Layout Editor の詳細については、Layout Editor を使用して UI を作成するをご覧ください。

ConstraintLayout を使って作成できるさまざまなレイアウトについては、GitHub での制約レイアウトのサンプル プロジェクトをご覧ください。

制約の概要

ConstraintLayout 内でビューの位置を定義するには、ビューに対して最低 1 つの水平方向の制約と 1 つの垂直方向の制約を設定します。各制約は、別のビュー、親のレイアウト、非表示のガイドラインとの接続や位置揃えを表します。各制約は、垂直軸または水平軸に沿ったビューの位置を定義します。各ビューには、軸ごとに少なくとも 1 つの制約が必要ですが、多くの場合、さらに多くの制約が必要になります。

Layout Editor にビューをドロップすると、制約がない場合でも、配置した場所に残ります。これは編集を容易にするためのものです。デバイスでレイアウトを実行するときにビューに制約が設定されていない場合、ビューは [0,0](左上の隅)の位置に描画されます。

図 1 では、エディタではレイアウトは正常に表示されますが、ビュー C には垂直方向の制約はありません。このレイアウトをデバイス上で描画すると、ビュー C の水平方向は、ビュー A の左右の外辺と同じ位置になりますが、垂直方向の制約がないため、画面の一番上に表示されます。

図 1. エディタ内でビュー C はビュー A の下に表示されるが、垂直方向は制約がない。

図 2. ビュー C がビュー A の下に垂直方向に制約されました。

制約がない場合、コンパイル エラーは発生しませんが、Layout Editor のツールバーには、制約が欠落しているエラーとして表示されます。エラーやその他の警告を表示するには、[Show Warnings and errors] をクリックします。制約の欠落を回避するために、Layout Editor の自動接続と制約の推測機能を使用して、制約が自動的に追加されます。

ConstraintLayout をプロジェクトに追加する

プロジェクトで ConstraintLayout を使用する手順は次のとおりです。

  1. maven.google.com リポジトリが settings.gradle ファイル内に宣言されていることを確認します。

    Groovy

        dependencyResolutionManagement {
          ...
          repositories {
              google()
          }
        )
        

    Kotlin

        dependencyResolutionManagement {
          ...
          repositories {
              google()
          }
        }
        
  2. 次の例に示すように、モジュール レベルの build.gradle ファイルにライブラリを依存関係として追加します。最新バージョンは、例に示されているバージョンとは異なる場合があります。

    Groovy

    dependencies {
        implementation "androidx.constraintlayout:constraintlayout:2.2.0"
        // To use constraintlayout in compose
        implementation "androidx.constraintlayout:constraintlayout-compose:1.1.0"
    }

    Kotlin

    dependencies {
        implementation("androidx.constraintlayout:constraintlayout:2.2.0")
        // To use constraintlayout in compose
        implementation("androidx.constraintlayout:constraintlayout-compose:1.1.0")
    }
  3. ツールバーまたは同期の通知内で、[Sync Project with Gradle Files] をクリックします。

これで、ConstraintLayout を使ってレイアウトを作成する準備が整いました。

レイアウトを変換する

図 3. レイアウトを ConstraintLayout に変換するメニュー。

既存のレイアウトを制約レイアウトに変換する手順は次のとおりです。

  1. Android Studio でレイアウトを開き、エディタ ウィンドウの下部にある [Design] タブをクリックします。
  2. [Component Tree] ウィンドウで、レイアウトを右クリックして [Convert LinearLayout to ConstraintLayout] をクリックします。

新しいレイアウトを作成する

新しい制約レイアウト ファイルを作成する手順は次のとおりです。

  1. [Project] ウィンドウでモジュールのフォルダをクリックし、[File] > [New] > [XML] > [Layout XML] を選択します。
  2. レイアウト ファイルの名前を入力し、[Root Tag] に「androidx.constraintlayout.widget.ConstraintLayout」と入力します。
  3. [Finish] をクリックします。

制約を追加または削除する

制約を追加する手順は次のとおりです。

動画 1. ビューの左側は、親の左側に制約されます。

  1. ビューを [Palette] ウィンドウからエディタにドラッグします。

    ConstraintLayout 内にビューを追加すると、境界ボックスが表示され、4 つの隅に正方形のサイズ変更ハンドル、4 つの辺に円形の制約ハンドルが表示されます。

  2. ビューをクリックして選択します。
  3. 次のいずれかの操作を行います。
    • 制約ハンドルをクリックし、利用できるアンカー ポイントまでドラッグします。アンカー ポイントとは、別のビューの外辺、レイアウトの外辺、またはガイドラインです。Layout Editor で制約ハンドルをドラッグしていると、利用できる接続アンカーと青色のオーバーレイが表示されます。
    • 図 4 に示すように、[Attributes] ウィンドウの [Layout] セクションで [Create a connection] ボタンのいずれかをクリックします。

      図 4. [Attributes] ウィンドウの [Layout] セクションで接続を作成

制約を設定すると、エディタでデフォルトのマージンが設定され、2 つのビューが分離されます。

制約を設定する際は、以下のルールを念頭に置いてください。

  • ビューごとに、水平方向と垂直方向の制約が 1 つずつ必要です。
  • 同じ平面を共有する制約ハンドルとアンカー ポイントの間でのみ制約を設定できます。ビューの垂直面(左側と右側)は別の垂直面にのみ制約でき、ベースラインは他のベースラインにのみ制約できます。
  • 各制約ハンドルは 1 つの制約にしか使用できませんが、異なるビューから同じアンカー ポイントに複数の制約を作成できます。

制約を削除するには、次のいずれかを行います。

  • 制約をクリックして選択し、[削除] をクリックします。
  • 制約アンカーを Ctrl キーを押しながらクリック(macOS では Command キーを押しながらクリック)します。図 5 に示すように、制約が赤色になると、クリックして削除できます。

    図 5. 赤色の制約はクリックして削除できる

  • 図 6 に示すように、[Attributes] ウィンドウの [Layout] セクションで制約アンカーをクリックします。

    図 6. 削除するには、制約アンカーをクリックします。

動画 2. 既存の制約に対抗する制約の追加。

動画 2 に示すように、ビュー上に相反する複数の制約を追加すると、制約の線がバネのように巻き付き、相反する力を表します。ビューのサイズを「固定」または「コンテンツをラップ」に設定すると、その様子がはっきりわかります。その場合、ビューは制約間の中央に配置されます。あるいは、制約に合わせてビューのサイズを拡大する場合は、サイズを「制約に合致」に切り替えます。現在のサイズのままビューを中央以外に動かすには、制約のバイアスを調整します。

次のセクションで説明するように、制約を使ってさまざまなタイプのレイアウト動作を設定できます。

親の位置

ビューの辺と、レイアウトの対応する外辺の間に制約を設定します。

図 7 では、ビューの左側が親のレイアウトの左端に接続されています。マージンを使って端からの距離を定義できます。

図 7. 親に対する水平方向の制約。

順序

2 つのビューを配置する順序を垂直方向または水平方向で定義します。

図 8 では、B は常に A の右になるように制約され、C は A の下になるように制約されます。ただし、これらの制約はアライメントを意味しないため、B は引き続き上下に移動できます。

図 8. 水平方向と垂直方向の制約。

位置揃え

ビューの外辺を別のビューの同じ側の外辺と揃えます。

図 9 では、B の左側は A の左側と位置が揃っています。ビューの中心を揃えるには、両側について 1 つの制約を設定します。

ビューを制約から内側にドラッグすることで、位置揃えをオフセットできます。たとえば図 10 に示す B では、オフセットが 24 dp です。オフセットは、制約を設定したビューのマージンとして定義されます。

位置を揃えるすべてのビューを選択して、ツールバーの位置揃えアイコン をクリックし、位置揃えのタイプを選択します。

図 9. 水平方向の位置揃え制約。

図 10. 水平方向のオフセット付き位置揃え制約。

ベースラインの位置揃え

ビューのテキストのベースラインを別のビューのテキストのベースラインと揃えます。

図 11 では、B の 1 行目と A のテキストの位置を揃えています。

ベースラインの制約を設定するには、制約を適用するテキストビューを右クリックし、[Show Baseline] をクリックします。次に、テキストのベースラインをクリックし、そのラインを別のベースラインにドラッグします。

図 11. ベースラインの位置揃え制約。

ガイドラインに対する制約を設定する

垂直方向または水平方向のガイドラインを追加して、それに対するビューの制約を設定できます。ガイドラインはアプリのユーザーには表示されません。レイアウト内でガイドラインは、レイアウトの外辺から dp 単位またはパーセントを基準にして配置できます。

ガイドラインを作成するには、ツールバーのガイドライン アイコン をクリックし、[Add Vertical Guideline](垂直ガイドラインを追加)または [Add Horizontal Guideline](水平ガイドラインを追加)をクリックします。

位置を変更するには点線をドラッグします。測定モードを切り替えるにはガイドラインの端にある円をクリックします。

図 12. ガイドラインに対する制約が設定されたビュー。

バリアに対して制約を設定する

バリアとは、ガイドラインと同様に、ビューの制約を設定できる非表示の線のことです。ただし、バリアの位置は独自に定義されません。代わりに、バリアの位置は、その中に含まれるビューの位置に応じて移動します。これは、ビューを特定の 1 つのビューではなく、一連のビューに制限する場合に便利です。

たとえば図 13 に示すように、ビュー C の制約はバリアの右側に対して設定されています。このバリアは、ビュー A とビュー B の両方の「終端」(左から右へのレイアウトでは、右側)に設定されます。バリアは、ビュー A とビュー B のどちらの右側が最も右にあるかに応じて移動します。

バリアを作成する手順は次のとおりです。

  1. ツールバーのガイドライン アイコン をクリックし、[Add Vertical Barrier](垂直バリアを追加)または [Add Horizontal Barrier](水平バリアを追加)をクリックします。
  2. [Component Tree] ウィンドウでバリア内に含めるビューを選択してバリア コンポーネント内にドラッグします。
  3. [Component Tree] からバリアを選択し、[Attributes] ウィンドウを開いて、[barrierDirection] を設定します。

これで、別のビューからそのバリアに対して制約を設定できます。

バリア内にあるビューもそのバリアに対して制約を設定できます。これにより、どのビューが最も長いか、または最も高いかがわからない場合でも、バリア内ですべてのビューの位置を揃えることができます。

バリアの「最小」の位置を設定するために、バリア内にガイドラインを含めることもできます。

図 13. ビュー C はバリアに対して制約が設定され、バリアはビュー A とビュー B の両方の位置とサイズに応じて移動します。

制約バイアスを調整する

ビューの両側に制約を追加する(かつ、対応するビューサイズを「固定」または「コンテンツをラップ」に設定する)場合、ビューは 2 つの制約の中央に位置し、バイアスがデフォルトで 50% に設定されます。このバイアスを調整するには、動画 3 に示すように、[Attributes] ウィンドウ内でバイアス スライダをドラッグするか、ビューそのものをドラッグします。

あるいは、制約に合わせてビューのサイズを拡大する場合は、サイズを「制約に合致」に切り替えます

動画 3. 制約バイアスの調整。

ビューのサイズを調整する

図 14. ビューを選択すると、[Attributes] ウィンドウで 1 サイズ比、2 制約の削除、3 高さまたは幅モード、4 マージン、5 制約バイアスの各コントロールが使用可能になる。6 制約リスト内のいずれかをクリックして、Layout Editor 内の個々の制約をハイライト表示することもできる。

隅のハンドルを使ってビューのサイズを変更できますが、それによってサイズがハードコードされるので、コンテンツや画面のサイズに応じてビューのサイズが変更されることはなくなります。別のサイズ設定モードを選択するには、ビューをクリックしてエディタの右側に [Attributes] ウィンドウを開きます。

図 14 に示すように、[Attributes] ウィンドウの上部にはビューのインスペクタがあり、レイアウト属性のコントロールがいくつか利用できます。これは、制約付きレイアウト内のビューでのみ使用できます。

図 14 の 3 の吹き出しが示す記号をクリックすると、高さと幅の計算方法を変更できます。この記号は、次のようなサイズモードを表します。記号をクリックすると、次の設定が切り替わります。

  • 固定: 次のテキスト ボックスに特定のサイズを指定するか、エディタ内でビューのサイズを変更します。
  • コンテンツをラップ: ビューはコンテンツを収めるのに必要なだけ拡大します。
    • layout_restrictedWidth
    • 制約を尊重するように水平方向のディメンションを変更するには、これを true に設定します。デフォルトでは、WRAP_CONTENT に設定されたウィジェットは制約によって制限されません。

  • 制約に合致: ビューは各辺の制約を満たす範囲内で(ビューのマージンを考慮して)可能な限り大きく拡大します。ただし、この動作は以下の属性と値で変更できます。これらの属性は、ビューの幅を [match constraints] に設定した場合のみ有効になります。
    • layout_constraintWidth_min

      ビューの最小幅の dp サイズを指定します。

    • layout_constraintWidth_max

      ビューの最大幅の dp サイズを指定します。

    ただし、指定した高さまたは幅に制約が 1 つしか設定されていない場合、ビューはそのコンテンツが収まるように拡大されます。このモードを高さまたは幅に使用すると、サイズ比率を設定できます。

サイズを比として設定する

図 15. アスペクト比を 16:9 に設定し、ビューの高さを基準

少なくとも 1 つのビュー ディメンションが「制約に一致」(0dp)に設定されている場合、ビューサイズを 16:9 などの比率に設定できます。この比率を有効にするには、[Toggle Aspect Ratio Constraint](図 14 の図番号 1)をクリックして、表示される入力に width:height の比率を入力します。

幅と高さの両方が「制約に合わせる」に設定されている場合、[アスペクト比の制約を切り替え] をクリックすると、一方のアスペクト比に基づいてどちらの寸法を使用するかを選択できます。ビュー インスペクタでは、比として設定されている外辺を実線で接続します。

たとえば、両方を「制約に合致」に設定した場合は、[Toggle Aspect Ratio Constraint] を 2 回クリックして、幅を高さの比として設定します。全体のサイズはビューの高さによって決まります。ビューの高さは自由に定義できます(図 15 を参照)。

ビューのマージンを調整する

ビューの間隔を均等にするには、ツールバーのマージン アイコン をクリックして、レイアウトに追加する各ビューにデフォルトのマージンを選択します。デフォルトのマージンを変更する場合、その後で追加したビューにのみ変更が適用されます。

[Attributes] ウィンドウ内の各ビューのマージンをコントロールするには、各制約を表す線の上の数字をクリックします。図 14 のコールアウト 4 は、下余白が 16 dp に設定されていることを示しています。

図 16. ツールバーのマージン アイコン

このツールで提供されるマージンはすべて 8 dp の係数であり、ビューをマテリアル デザインの 8 dp 正方形のグリッドの推奨事項に合わせて調整します。

チェーンを使って線形グループを管理する

図 17. 2 つのビューの水平方向チェーン。

チェーンとは、双方向の位置制約を設定して、相互にリンクさせたビューのグループです。チェーン内のビューは、垂直方向または水平方向に分散させることができます。

図 18. 各チェーン スタイルの例。

チェーンには、次のいずれかの方法でスタイルを設定できます。

  1. Spread: ビューはマージンを考慮した後、均等に配置されます。これがデフォルトです。
  2. Spread inside: 最初のビューと最後のビューは、チェーンの両端の制約に固定され、残りのビューは均等に配置されます。
  3. 重み付け: チェーンが spread または spread inside に設定されている場合、1 つ以上のビューを「制約を一致」に設定(0dp)することで、残りのスペースを埋めることができます。デフォルトでは、スペースは「制約を一致」に設定された各ビューに均等に分散されますが、layout_constraintHorizontal_weight 属性と layout_constraintVertical_weight 属性を使用して、各ビューに重要度の重み付けを割り当てることができます。これは、リニア レイアウトlayout_weight と同じように機能します。ウェイト値が最も高いビューが最も多くのスペースを占め、ウェイトが同じビューは占めるスペースも同じになります。
  4. Packed: ビューは(マージンを考慮して)連続して配置されます。チェーンの「ヘッド」ビューのバイアスを変更することで、チェーン全体のバイアス(左右または上下)を調整できます。

チェーンのヘッドビュー(水平チェーン(左から右レイアウト)の左端のビューと垂直チェーンの最上部のビュー)は、XML でチェーンのスタイルを定義します。ただし、チェーン内のビューを選択し、ビューの下に表示されるチェーンボタン をクリックすることで、展開、内部展開、パックを切り替えることができます。

チェーンを作成するには、動画 4 に示すように次の操作を行います。

  1. チェーンに含めるすべてのビューを選択します。
  2. いずれかのビューを右クリックします。
  3. [チェーン] を選択します。
  4. [水平方向の中央] または [垂直方向の中央] を選択します。

動画 4. 水平方向のチェーンを作成します。

チェーンを使用する際には、以下の点も検討してください。

  • 同じビューを水平方向と垂直方向の 2 つのチェーンに含めて、柔軟なグリッド レイアウトを作成できます。
  • チェーンの両端で同じ軸上のそれぞれ別のオブジェクトに対して、図 14 に示すように、制約が設定されている場合のみ、チェーンは正しく機能します。
  • チェーンの向きは垂直か水平かのいずれかですが、チェーンを 1 つ設定しても、各ビューのその方向の位置を揃えることはできません。チェーン内の各ビューを適切に配置するには、位置揃えの制約などの他の制約も含めてください。

制約を自動的に設定する

レイアウトに配置する際、すべてのビューに制約を追加するのではなく、各ビューを Layout Editor で目的の位置に移動してから、制約の推測アイコン をクリックすると、制約を自動的に設定できます。

制約の推測機能は、レイアウトをスキャンして、すべてのビューについて最も効果的な制約セットを判断します。柔軟性を持たせながら、現在の位置にビューを配置する制約を設定します。想定するさまざまな画面のサイズや向きにレイアウトが正しく応じるようにするには、デベロッパーによる調整が多少必要になることもあります。

親への自動接続は、デベロッパーが有効にできる独立した機能です。有効にして子ビューを親に追加すると、この機能により、レイアウトに追加した各ビューに 2 つ以上の制約が自動的に作成されます。ただしこれは、親のレイアウトに対して制約が適切に設定できるときのみです。自動接続では、レイアウト内の他のビューに対する制約が設定されることはありません。

自動接続はデフォルトで無効です。有効にするには、Layout Editor のツールバーで親への自動接続を有効にするアイコン をクリックします。

キーフレーム アニメーション

ConstraintLayout 内では、要素のサイズや位置の変更を、ConstraintSetTransitionManager を使ってアニメーションにすることができます。

ConstraintSet は、ConstraintLayout 内のすべての子要素の制約、マージン、パディングを表す軽量のオブジェクトです。表示する ConstraintLayoutConstraintSet を適用すると、レイアウトはそのすべての子要素の制約を更新します。

ConstraintSet を使用してアニメーションを作成するには、アニメーションの開始と終了のキーフレームとして機能する 2 つのレイアウト ファイルを指定します。これで、2 番目のキーフレーム ファイルから ConstraintSet を読み込み、表示する ConstraintLayout に適用することができます。

次のコード例で、画面下部にボタン 1 個を移動する場合のアニメーション化の方法を示します。

// MainActivity.kt

fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.keyframe_one)
    constraintLayout = findViewById(R.id.constraint_layout) // member variable
}

fun animateToKeyframeTwo() {
    val constraintSet = ConstraintSet()
    constraintSet.load(this, R.layout.keyframe_two)
    TransitionManager.beginDelayedTransition()
    constraintSet.applyTo(constraintLayout)
}
// layout/keyframe1.xml
// Keyframe 1 contains the starting position for all elements in the animation
// as well as final colors and text sizes.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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">

    <Button
        android:id="@+id/button2"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
// layout/keyframe2.xml
// Keyframe 2 contains another ConstraintLayout with the final positions.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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">

    <Button
        android:id="@+id/button2"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

参考情報

ConstraintLayoutSunflower デモアプリで使用されています。