Kotlin(Java)コード上でConstraintLayoutの制約を編集する


みなさん、こんにちは。空中清高です。


突然ですが、ConstraintLayout使っていますか？
ConstraintLayoutは表現力が高く、しかもパフォーマンスもよくて、いいことずくめなViewです。
参考:[ConstraintLayout がもたらすパフォーマンスのメリットを理解する](https://developers-jp.googleblog.com/2017/09/understanding-performance-benefits-of.html)

最近はConstraintLayoutばかり使っているのですが、ある日どうしてもConstraintLayoutを使って動的なレイアウトを作成したくなりました。
そういうことってよくありますよね？
そこでいろいろ探しているうちに、コード上からConstraintLayoutの制約を設定するのに便利なConstraintSetを見つけたので紹介したいと思います。



# まずはxmlでConstraintLayoutを使う

公式リファレンスにxmlから設定する方法が詳しく載っています。
[公式リファレンス](https://developer.android.com/reference/android/support/constraint/ConstraintLayout.html)

またたくさんの人がxmlで設定して利用する方法を紹介してくれています。
たとえば、「ConstraintLayout」でググってみると
[@Yuki312さんの記事](http://yuki312.blogspot.jp/2017/03/constraintlayout.html)や
[@tomoima525さんのQiita記事「Androidの新しいLayout、ConstraintLayoutことはじめ」](https://qiita.com/tomoima525/items/0584be581a0d3a4db8c5)
など画像付きでわかりやすい解説記事がたくさん見つかると思います。
いつも助かっています、ありがとうございます。

# コードからConstraintLayoutを設定する

さて本題のコードからConstraintLayoutを設定する方法です。
まずはConstraintLayoutのインスタンスを取得しましょう。
すでにxml等から生成されているならfindViewById()等で探して、これから生成するならコンストラクタで生成します。

```kotlin
val constraintLayout = findViewById(R.id.constraint_layout)
```

```kotlin
val constraintLayout = ConstraintLayout(context)
```

こうやって取得した`constraintLayout`のレイアウトを動的に変更したいとします。
たとえば`constraintLayout`の中央にTextViewを追加したくなったら、こう書きます。

```kotlin
// TextViewを生成して追加
val textView = TextView(context).apply { id = View.generateViewId() }
constraintLayout.addView(textView)

// ConstraintSetを生成してConstraintLayoutから制約を複製する
val constraintSet = ConstraintSet()
constraintSet.clone(constraintLayout)

// TextViewを中央にセット
constraintSet.connect(textView.id, ConstraintSet.LEFT, ConstraintSet.PARENT_ID, ConstraintSet.LEFT)
constraintSet.connect(textView.id, ConstraintSet.RIGHT, ConstraintSet.PARENT_ID, ConstraintSet.RIGHT)

// 編集した制約をConstraintLayoutに反映させる
constraintSet.applyTo(constraintLayout)
```

# コードの解説

ConstraintSetはConstraintLayoutの制約を設定するときに便利なクラスです。
デフォルトコンストラクタで生成したあと、`clone`を使って制約を編集したいConstraintLayoutから設定を複製してください。

```kotlin
val constraintSet = ConstraintSet()
constraintSet.clone(constraintLayout)
```

次に`ConstraintSet#connect(int startID, int startSide, int endID, int endSide)`を使って制約を追加します。
startIDで指定されるviewをstartView、endIDで指定されるviewをendViewとすると、この制約

```kotlin
constraintSet.connect(startID, ConstraintSet.LEFT, endID, ConstraintSet.RIGHT)
```

は以下の図のようになっています。(橙色の線が制約です)

![connect.png](https://qiita-image-store.s3.amazonaws.com/0/97301/b4733d47-6dcd-7600-8b6d-247472346bd6.png)

また、ConstraintSetで制約を編集するときにxmlの"parent"と対応するのは`ConstraintSet.PARENT_ID`です。
なので

```kotlin
constraintSet.connect(textView.id, ConstraintSet.LEFT, ConstraintSet.PARENT_ID, ConstraintSet.LEFT)
constraintSet.connect(textView.id, ConstraintSet.RIGHT, ConstraintSet.PARENT_ID, ConstraintSet.RIGHT)
```

と書くと、xmlでTextViewに
`app:layout_constraintLeft_toLeftOf="parent"`
`app:layout_constraintRight_toRightOf="parent"`
と指定したときと同じ制約になります。

最後に、これまで編集した`constraintSet`を反映させるには

```kotlin
constraintSet.applyTo(constraintLayout)
```

というふうに`applyTo`を使います。

# ConstraintSetの便利メソッド

ConstraintSetのconnectは基本的なメソッドですが、他にも便利なメソッドがたくさんあります。
たとえばconnectに第五引数を渡すと、制約の追加と同時にマージンが指定できますし、`clear`で制約を消したり、今回の中央寄せのショートカットメソッド`centerHorizontally`などもあります。
また[@takahiromさんがQiita記事](https://qiita.com/takahirom/items/97818748e2c2059a2536)で紹介してくれているChainを利用するための
`createHorizontalChain,addToHorizontalChain,setHorizontalChainStyle`などや
GuideLineを利用するための`setGuidelineBegin,setGuidelineEnd,setGuidelinePercent`や
GuideLineを生成する`ConstraintSet#create(int guidelineID, int orientation)`などもあります。

このようにxmlで設定できることはConstraintSetで簡単にできるので、
みなさんもぜひConstraintSetを使ってConstraintLayoutの動的なレイアウトを作ってみましょう！

# まとめ

* ConstraintLayoutの動的なレイアウトにはConstraintSetが便利
* ConstraintSetはcloneしてapplyToする
* ConstraintSetには便利なメソッドがいっぱい