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 つの制約が必要ですが、多くの場合、より多くの制約が必要になります。

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

図 1 では、エディタではレイアウトに問題はありませんが、ビュー C に垂直方向の制約はありません。このレイアウトをデバイス上に描画すると、ビュー C はビュー A の左右端に水平方向に位置揃えされます。ただし、垂直方向の制約がないため、ビュー C は画面の上部に表示されます。

図 1. エディタではビュー A の下にビュー C が表示されますが、垂直方向の制約はありません。

図 2. ビュー C がビュー A の下に垂直方向の制約を受けるようになりました。

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

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

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

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

    Groovy

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

    Kotlin

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

    Groovy

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

    Kotlin

    dependencies {
        implementation("androidx.constraintlayout:constraintlayout:2.2.0-alpha13")
        // To use constraintlayout in compose
        implementation("androidx.constraintlayout:constraintlayout-compose:1.1.0-alpha13")
    }
    
  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 にビューを追加すると、四隅に正方形のサイズ変更ハンドルがあり、両側に円形の制約ハンドルがある境界ボックスに表示されます。

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

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

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

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

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

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

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

    図 5. 赤色の制約はクリックで削除できることを示しています。

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

    図 6. 制約アンカーをクリックして削除します。

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

ビューに相反する制約を追加すると、動画 2 に示すように、制約線がバネのように巻き付けられ、反対の力を示します。ビューサイズが「fixed」または「wrap content」に設定されている場合に、その効果が最も顕著になります。この場合、ビューは制約間の中央に配置されます。制約に合わせてビューのサイズを拡大する場合は、サイズを切り替えて「制約に合致」させます。現在のサイズを維持したまま、ビューが中央に揃わないように移動する場合は、制約バイアスを調整します。

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

親の位置

ビューの側面をレイアウトの対応する端に制限します。

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

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

順序

2 つのビュー(垂直方向または水平方向)の表示順序を定義します。

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

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

配置

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

図 9 では、B の左側と A の左側が一致しています。ビューの中心を揃えたい場合は、両側に制約を作成します。

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

配置するすべてのビューを選択し、ツールバーの配置アイコン をクリックして配置の種類を選択することもできます。

図 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% に設定されます。バイアスを調整するには、[Attributes] ウィンドウのバイアス スライダーをドラッグするか、動画 3 に示すようにビューをドラッグします。

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

動画 3. 制約バイアスを調整する。

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

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

角のハンドルを使用してビューのサイズを変更できますが、この場合、サイズがハードコードされます。つまり、コンテンツや画面サイズに応じてビューがサイズ変更されることはありません。別のサイズモードを選択するには、ビューをクリックして、エディタの右側にある [Attributes] ウィンドウを開きます。

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

図 14 のコールアウト 3 で示されている記号をクリックすると、高さと幅の計算方法を変更できます。これらの記号は、次のようにサイズモードを表します。アイコンをクリックすると、次の設定を切り替えられます。

  • 固定: 次のテキスト ボックスで特定のディメンションを指定するか、エディタでビューのサイズを変更します。
  • コンテンツをラップ: ビューは、コンテンツを収めるために必要な数だけ拡大します。
    • layout_constraintWidth(レイアウトの制約付き幅)
    • 制約に合わせて水平方向の寸法を変更するには、これを 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] をクリックして、もう一方の比率に基づくサイズを選択できます。ビュー インスペクタでは、対応する辺を実線で接続することで、比率として設定されているディメンションが示されます。

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

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

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

[Attributes] ウィンドウで各ビューのマージンを制御するには、各制約を表す線の番号をクリックします。図 14 の 4 は、下余白が 16 dp に設定されていることを示しています。

図 16. ツールバーの [マージン] ボタン。

このツールで提供されるマージンはすべて 8 dp の係数であるため、ビューをマテリアル デザインの 8 dp 正方形のグリッドの推奨値に合わせることができます。

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

図 17. 2 つのビューを持つ水平チェーン。

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

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

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

  1. Spread: マージンを考慮に入れ、ビューは均等に分散されます。これがデフォルトです。
  2. 内部展開: 最初と最後のビューはチェーンの両端の制約が適用され、残りは均等に分散されます。
  3. 重み付き: チェーンを spread または spread internal に設定した場合、1 つ以上のビューを「制約に合致」(0dp)に設定することで残りのスペースを埋めることができます。デフォルトでは、「制約に合致」に設定された各ビューにスペースが均等に分配されますが、layout_constraintHorizontal_weight 属性と layout_constraintVertical_weight 属性を使用して各ビューに重要度の重みを割り当てることができます。 これは、線形レイアウトlayout_weight と同じように機能します。つまり、重みが最も高いビューが最も大きいスペースを占有し、同じ重みのビューも同じスペース量を取得します。
  4. パック: ビューはマージンを考慮したうえで一緒にパックされます。チェーンの「ヘッド」ビューのバイアスを変更することで、チェーン全体のバイアス(左右または上下)を調整できます。

チェーンの「ヘッド」ビュー(横方向のチェーン(左から右のレイアウト)の左端のビューと縦方向のチェーンの一番上のビュー)は、チェーンのスタイルを XML で定義します。ただし、チェーン内の任意のビューを選択して、ビューの下に表示されるチェーンボタン をクリックすることで、spreadspread internalpacked を切り替えることができます。

チェーンを作成する手順は次のとおりです。動画 4 をご覧ください。

  1. チェーンに含めるすべてのビューを選択します。
  2. いずれかのビューを右クリックします。
  3. [チェーン] を選択します。
  4. [中央揃え] または [上下中央揃え] を選択します。

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

チェーンを使用する際は、次の点を考慮してください。

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

制約を自動的に設定する

レイアウト内に配置する際にすべてのビューに制約を追加する代わりに、Layout Editor 内の目的の位置に各ビューを移動し、[Infer Constraints] をクリックして制約を自動的に作成できます。

[Infer Constraints] は、レイアウトをスキャンして、すべてのビューに対して最も効果的な制約のセットを判断します。これにより、柔軟性を実現しつつ、ビューを現在の位置に制限します。さまざまな画面サイズと向きで意図したとおりにレイアウトが応答するように、調整が必要になることがあります。

[親への自動接続] は、有効にできる別個の機能です。この機能が有効になっていて、子ビューを親に追加すると、ビューをレイアウトに追加すると、各ビューに 2 つ以上の制約が自動的に作成されます。これは、ビューを親レイアウトに制限するのが適切な場合に限られます。自動接続によって、レイアウト内の他のビューに対する制約が生じることはありません。

自動接続はデフォルトで無効です。これを有効にするには、Layout Editor ツールバーの [Enable Autoconnection to Parent] をクリックします。

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

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 デモアプリで使用されています。