ConstraintLayout入門その6 - チェーン

ConstraintLayoutを使用するための設定については、[ConstraintLayout入門その1](https://qiita.com/yamadacsa/items/a4fcf4de9982111cdb7c)をご覧ください。

## ConstraintLayoutのチェーン

ConstraintLayoutの子Viewが形成する**チェーン**とは、位置を相互に制約し合っている、2つ以上の要素からなる子Viewの集合を指します。チェーンには水平方向のチェーンと垂直方向のチェーンがあり、子Viewは水平方向のチェーンと垂直方向のチェーンの最大2つのチェーンに属することができます。水平方向のチェーンは子Viewの左端と右端を、垂直方向のチェーンは子Viewの上端と下端を制約によって決定するためのものであり、2つのチェーンに属している子Viewの水平方向の位置と垂直方向の位置は原則として2つのチェーンによって独立に決定されます。

chain_horizontal.xmlは水平方向のチェーンの例です。この場合、 ```<StartView>```, ```<MidView>```, ```<EndView>```, の3つの子Viewが同じ水平方向のチェーンに所属します。

```xml:chain_horizontal.xml
<androidx.constraintlayout.widget.ConstraintLayout
    ....
    >

    <StartView
        android:id="@+id/startView"
        ...
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toStartOf="@id/MidView"
        app:layout_constraintHorizontal_chainStyle="spread"
        ...
    />

    <MidView
        android:id="@+id/midView"
        ...
        app:layout_constraintStart_toEndOf="@id/startView"
        app:layout_constraintEnd_toStartOf="@id/endView"
        ...
    />

    <EndView
        android:id="@+id/endView"
        ...
        app:layout_constraintStart_toEndOf="@id/midView"
        app:layout_constraintEnd_toEndOf="parent"
        ...
    />

</androidx.constraintlayout.widget.ConstraintLayout>
```

chain_vertical.xmlは垂直方向のチェーンの例です。この場合、 ```<TopView>```, ```<MidView>```, ```<BottomView>```, の3つの子Viewが同じ垂直方向のチェーンに所属します。他方、 ```<TitleView>``` は ```<TopView>``` に制約されているものの自らは他のViewを制約しておらず、「相互に制約し合う」という状態にはないので、チェーンには含まれません。

```xml:chain_vertical.xml
<androidx.constraintlayout.widget.ConstraintLayout
    ....
    >

    <TitleView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
    />

    <TopView
        android:id="@+id/topView"
        ...
        app:layout_constraintTop_toBottomOf="@id/title"
        app:layout_constraintBottom_toTopOf="@id/MidView"
        app:layout_constraintVertical_chainStyle="packed"
        ...
    />

    <MidView
        android:id="@+id/midView"
        ...
        app:layout_constraintTop_toBottomOf="@id/topView"
        app:layout_constraintBottom_toTopOf="@id/bottomView"
        ...
    />

    <BottomView
        android:id="@+id/bottomView"
        ...
        app:layout_constraintTop_toBottomOf="@id/midView"
        applayout_constraintBottom_toBottomOf="parent"
        ...
    />

</androidx.constraintlayout.widget.ConstraintLayout>
```

ここで、 ```app:layout_constraintHorizontal_chainStyle``` はチェーン中の一番左側の子Viewに、 ```app:layout_constraintVertical_chainStyle``` はチェーン中の一番上側の子Viewに設定しなければならず、他の子Viewに設定しても無視されます。後述するように、1つのチェーンには1つのチェーンスタイルのみが設定できますので、チェーン中の「ヘッド」Viewにのみ設定できることになっています。

## chainStyleとbias

```app:layout_constraintHorizontal_chainStyle``` または ```app:layout_constraintVertical_chainStyle``` を用いて、1つのチェーンに対して ```"spread"```, ```"spread_inside"```, ```"packed"``` の3つのうち1つの "chainStyle" を指定できます。チェーンスタイルが "packed" の場合、さらに ```app:layout_constraintHorizontal_bias``` または ```app:layout_constraintVertical_bias``` で1つのbiasを設定できます

[ConstraintLayout でレスポンシブ UI を作成する](https://developer.android.com/training/constraint-layout?hl=ja) では、チェーンのスタイルとして 'Spread', 'Spread inside', 'Weighted', 'Packed' の4通りを示しています。混乱しそうなところですが、

- "chainStyle" が ```"spread"``` または ```"spread_inside"``` 、かつ、チェーンに含まれる1つ以上の子Viewにおけるチェーンの方向の制約が「制約に一致」であった場合は 'Weighted' チェーンになります（水平方向のチェーンの場合は ```android:layout_width="0dp"``` のとき、垂直方向のチェーンの場合は ```android:layout_height="0dp"``` のとき「制約に一致」）。
- "chainStyle" が ```"spread"``` 、かつ、チェーンに含まれる子Viewのうちチェーンの方向の制約が「制約に一致」になるものがひとつもない場合は 'Spread' チェーンになります。
- "chainStyle" が ```"spread_inside"``` 、かつ、チェーンに含まれる子Viewのうちチェーンの方向の制約が「制約に一致」になるものがひとつもない場合は 'Spread inside' チェーンになります。
- "chainStyle" が ```"packed"``` の場合は 'Packed' チェーンになります。

ただ、筆者が試した限りでは、 "chainStyle" が ```"packed"``` であった場合でも、1つ以上の子Viewが「制約に一致」であれば 'Weighted' チェーンになるようです（ConstraintLayoutのバージョン1.1.3と2.0.0-rc1の両方でテストしています）。 'Weighted' チェーンを構成するときは、 "chainStyle" の指定を省略することをお勧めします（ "chainStyle" 省略時のデフォルト値は ```"spread"``` ）。

## 4つのチェーンスタイル

4つのチェーンスタイルを混在させたConstraintLayoutの表示例を以下に示します。
<img width="360" alt="表示結果" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221967/ce4d2d51-cf48-3371-2631-204d570b3b45.png">

- 1, 2, 3, 4: 水平方向のSpreadチェーン
- 5, 6, 7, 8: 水平方向のSpread insideチェーン
- 9, A, B, C: 水平方向のWeightedチェーン
- D, E, F, 0: 水平方向のPackedチェーン
- 1, 5, 9, D: 垂直方向のSpreadチェーン
- 2, 6, A, E: 垂直方向のSpread insideチェーン
- 3, 7, B, F: 垂直方向のWeightedチェーン
- 4, 8, C, 0: 垂直方向のPackedチェーン

となっています。チェーンは水平方向もしくは垂直方向の位置の制約を他の次元とは独立に制御するので、チェーンを構成する子Viewが水平ないし垂直に整列していなくても使えます。この点は、旧来からよく使われているLinearLayoutよりも柔軟といえます。

4種のチェーンは、チェーンに与えられた長さの分配方法がそれぞれ異なります。
Spreadチェーンは、チェーン内の子View同士の距離とチェーンの外側との距離がすべて等しくなるように、それぞれの子Viewの位置が決まります。ここで、biasのようなパラメータを使って距離に偏りを生じさせるような設定はできません（biasが設定できるのはPackedチェーンのみです）。
Spread insideチェーンは、チェーン内の子View同士の距離がすべて等しくなるように、それぞれの子Viewの位置が決まります。チェーンの外側に空間を生じないのがSpreadチェーンとの違いです。Spreadチェーンと同様、距離に偏りを生じさせることはできません。

Weightedチェーンは、チェーン中の「制約に一致」でない子Viewに長さを割り当てた後の残りの長さを「制約に一致」が設定された子Viewに分配します。分配は、「制約に一致」が設定された子Viewのweight（ ```app:layout_constraintHorizontal_weight``` または ```app:layout_constraintVertical_weight``` の値）にしたがって比例配分されます。残りの長さをすべて子Viewのいずれかに分配しますので、SpreadチェーンやSpread insideチェーンのような子View間の距離は発生しません。
Packedチェーンは、子Viewに割り当てられない長さはすべてチェーンの外側の距離のみに割り当てられ、子View同士の距離には割り当てられません。Packedチェーンのみ「ヘッド」Viewにbiasを設定することで表示を調整することができ（値は0〜1）、 ```app:layout_constraintHorizontal_bias``` でチェーンの "Left" もしくは "Start" の側の距離の比率、 ```app:layout_constraintVertical_weight``` でチェーンの上側の距離の比率を表します。

## サンプルコード

今回のサンプルコードは以下のリポジトリにあります。
https://github.com/csayamada/ConstraintLayout6

結局のところチェーンにはSpread、Spread inside、Weighted、Packedの4通りしかない、と考えれば、さほど難しいものではありません。「位置を相互に制約する」という状態も、慣れてしまえばそれほどわかりにくいものではありません。「位置を相互に制約しあう複数の子View」が形成されたとき、それがチェーンとなり、4つのうちいずれかのモードで配置が調整される、ということです。

チェーンのさまざまな性質は、サンプルコードの制約の設定をさまざまに変えたときにどう変化するかを試せば理解しやすいと思います。Android Studioのレイアウト画面のDesignビューを使い慣れている方は、DesignビューのUIを有効活用してさまざまな調整を試すと良いでしょう。

## 参考文献

[ConstraintLayout でレスポンシブ UI を作成する | Android デベロッパー](https://developer.android.com/training/constraint-layout?hl=ja)
[ConstraintLayout | Android Developers](https://developer.android.com/reference/androidx/constraintlayout/widget/ConstraintLayout)