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

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

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

このページは、Android Studio 3.0 以降で ConstraintLayout を使ってレイアウトを作成する際のガイドです。Layout Editor そのものの詳細については、Android Studio ガイドの 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 のツールバーには、制約が欠落しているエラーとして表示されます。エラーまたは警告を表示するには、警告とエラーの表示アイコン をクリックします。制約の欠落を避けるため、Layout Editor では自動接続と制約の推測の機能を使って自動的に制約を追加することができます。

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

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

  1. maven.google.com リポジトリをモジュール レベルの build.gradle ファイル内に必ず宣言します。
        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 layout 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] セクションで接続の作成ボタン のいずれかをクリックします。

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

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

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

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

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

  • 制約をクリックして選択し、Delete を押します。
  • Control(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_constraintWidth_default
      • spread: 各辺の制約を満たす範囲内で可能な限りビューが拡大します。これがデフォルト設定です。
      • wrap: コンテンツが収まるのに必要なだけビューが拡大しますが、制約が適用される場合、ビューがそれより小さくなることがあります。この設定と、上記の「コンテンツをラップ」の設定の違いは、「コンテンツをラップ」に指定すると、幅がコンテンツの幅と常に完全に一致するのに対し、「制約に一致」を指定して layout_constraintWidth_defaultwrap を指定すると、ビューの幅がコンテンツの幅より小さくなることがあります。
    • layout_constraintWidth_min

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

    • layout_constraintWidth_max

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

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

: ConstraintLayout 内のビューには match_parent は指定できません。代わりに「制約に合致」(0dp)を使用してください。

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

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

ビューサイズの少なくとも 1 つを「制約に合致」に設定する場合(0dp)、ビューのサイズを 16:9 のような比に設定できます。この比を有効にするには、[Toggle Aspect Ratio Constraint](図 14 の吹き出し 1)をクリックし、表示される入力欄に、「:高さ」の比を入力します。

幅と高さを両方とも「制約に合致」に設定する場合、[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. Spread inside: 最初のビューと最後のビューは、チェーンの両端の制約に固定され、残りのビューは均等に配置されます。
  3. Weighted: チェーンを [spread] または [spread inside] のどちらかに設定する際、1 つまたは複数のビューを「制約に合致」(0dp)に設定して拡大し、残りのスペースを埋めることができます。デフォルトでは、「制約に合致」に設定した各ビューにスペースが均等に分配されます。ただし、layout_constraintHorizontal_weight 属性または layout_constraintVertical_weight 属性を使って各ビューの重要度のウェイトを割り当てた場合は、均等ではなくなります。線形レイアウトlayout_weight をご存じであれば、これは同じように機能します。つまり、最大のウェイト値を持つビューが最も多くのスペースを占めます。ウェイトが同じビューは占めるスペースも同じになります。
  4. Packed: ビューは(マージンを考慮して)連続して配置されます。その後、チェーンのヘッドビューのバイアスを変更して、チェーン全体のバイアス(左右または上下)を調整できます。

チェーンの「ヘッド」ビュー(水平方向チェーンでは最も左にあるビュー、垂直方向チェーンでは最も上にあるビュー)が、そのチェーンの XML でのスタイルを規定します。ただし、チェーン内のいずれかのビューを選択し、そのビューの下に表示されるチェーン アイコン をクリックすることで、[spread]、[spread inside]、[packed] を切り替えられます。

チェーンを作成するには、動画 4 に示すように、チェーンに含めるビューをすべて選択し、いずれかのビューを右クリックして [Chain] を選択します。次に [Center Horizontally](水平方向の中央)または [Center Vertically](垂直方向の中央)を選択します。

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

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

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

制約を自動的に設定する

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

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

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

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

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

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

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

ConstraintSets を使ってアニメーションを作成するには、アニメーションの開始と終了のキーフレームとして機能する 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 デモアプリで使用されています。