【Android】ConstraintHelperの使い方


本記事はAndroid #2 Advent Calendarの22日目の記事になります。
初の参加で若干緊張しています。。。。

はじめに

現在、AndroidのUI実装ではConstraintLayoutを利用することがほとんどだと思います。
今回はConstraintLayoutの一つの、ConstraintHelperについて紹介したいと思います。

なお、今回使用するバージョンは、ベータ版のConstraintLayout 2.0.0-beta2を使用しているので、後ほど内容が変更になる可能性がありますので、その点はご了承ください。
また、変更がかかった際は、再度こちらの記事にも更新をかけようと考えています。

ConstraintHelperとは

複数のViewに対して同じ操作を行うためのものと認識しています。
ConstraintHelper自体の実装はViewを拡張した抽象クラスであり、このクラスを独自に拡張することもサポートされているようですので、非常に便利です。

ConstraintHelperへ反映させたいViewを指定するときは、app:constraint_referenced_idsにidをコンマ区切りで格納することで指定できます。これは基本的にどのConstraintHelperでもこの方式を用いており、共通仕様となっております。

独自で拡張するまでもなく、すでにConstraintHelperを拡張したクラスとして、以下が存在します。

すでにご存知のものも多いと思いますが、一つずつ軽く説明します。(VirtualLayoutは抽象クラスですので飛ばします。)

Barrier

下のように、二つの並んだViewに基準線をもうけます。これがバリアーと呼ばれるConstraintHelperであり、そこに Constraint をつけることで、片方の大きさが変わっても、基準線は変動してViewが重なるということは無くなります。これは結構使ったことのある人が多いのではないでしょうか。

Flow

特定のViewを指定した方向に並べます。方向としては、HorizontalVerticalが存在し、それぞれの並び方を以下に示します。こちらは最近リリースされた機能になるので、初めての方も多いと思います。

Horizontal

Vertical

Group

まとめてViewのVisibilityを変更したい時に使用します。
GroupのVisibilityを変更すると、それに応じて、中身のViewのVisibilityも一括で変更されます。
こちらはかなり有名なので、愛用している人も多いと思います。

Layer

Viewをまとめて操作したい時に使用します。

今回はLayerを使用して特別なことはしていませんが、app:constraint_referenced_idsにViewを追加してみると、下のようにまとめてくれましたので、今までLayoutをネストしてUI実装してた箇所が不要になると思います。

MotionHelper

主にMotionLayoutで使用するConstraintHelperのようです。
今回の記事ではConstraintHelperがメインですので、割愛します。

ConstraintHelperの拡張

上でも書いた通り、ConstraintHelperは拡張することを公式にサポートしています!
ここでは、実際に拡張しながら使い方を見ていきたいと思います。

独自実装時に継承するメソッド(変更される可能性あり)

独自実装をする時によく継承するメソッドを洗い出しておきますが、全て継承する必要はありません。

  • init(attrs: AttributeSet)
    • その名の通り、初期化メソッドです。
    • attrsで属性の内容を取ることができます。
  • updatePreLayout(container: ConstraintLayout)
    • 初めonMeasureが呼び出される時に実行される。
  • updatePostLayout(container: ConstraintLayout)
    • onLayoutが呼び出される時に実行される
  • updatePostMeasure(container: ConstraintLayout)
    • onMeasureが呼び出されるたびに実行される。
  • updatePostConstraints(container: ConstraintLayout)
    • constraintsに変更があるたびに呼び出されます。

実装例

今回は指定したViewの背景色を一括で変更するためのConstraintHelperを作りましょう。
名前はBackgroundHelper.ktとします。

attr.xmlはトピックから外れるので隠しておきます。


attr.xml
attr.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="BackgroundHelper">
        <attr name="backgroundColor" format="color" />
    </declare-styleable>
</resources>


BackgroundHelper.kt
class BackgroundHelper @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : ConstraintHelper(context, attrs, defStyleAttr) {
    private lateinit var backgroundColor: Color

    override fun init(attrs: AttributeSet?) {
        super.init(attrs)
        // 設定する背景色を取得する
        val array = context.obtainStyledAttributes(
            attrs,
            R.styleable.BackgroundHelper
        )

        backgroundColor = array.getColor(
            R.styleable.BackgroundHelper_backgroundColor,
            -1
        ).let {
            Color.valueOf(it)
        }

        array.recycle()
    }

    override fun updatePreLayout(container: ConstraintLayout?) {
        super.updatePreLayout(container)
        // はじめに`app:constraint_referenced_ids`に詰められた値をsetIdsでセットする (2.0.0-beta3からは不要)
        if (mReferenceIds != null) {
            setIds(mReferenceIds)
        }
    }

    override fun updatePostLayout(container: ConstraintLayout?) {
        // `app:constraint_referenced_ids`の個数をmCountで取得できる。
        // mIdsには`app:constraint_referenced_ids`で指定したIDが配列でつめられる
        mIds.take(mCount).mapNotNull { id ->
            container?.getViewById(id)
        }.forEach { view ->
            // それぞれのViewの背景色を指定した色へ変更する
            view.background = backgroundColor.toDrawable()
        }
    }
}

mIds.take(mCount)の部分はtakeせずに単純にmIdsforEachだけでも良いのでは? と思っているのですが、他のGroupBarrierでも同じ手法で実装されているので、毎回これを使っています。

実行

このBakgroundHelperを先ほどのTextView二つに適応させると以下のようになります。(色は#D81B60で指定しています。)

ConstraintHelperで個人的に一番感動したポイントなのですが、このようにbackgroundColorを変更すると、リアルタイムでその変更が適応されます。(画面左側)

最後に

今回はConstraintHelperというConstraintLayoutの一部について紹介しました。
ConstraintLayoutだけでは表現しきれないUIも、これを使えば実装ができるかもしれませんし、非常に将来性のある機能だと思っています。

2.0.0がstableになるのが待ち遠しいですね!

余談

ConstraintLayoutの最新版として、ConstraintLayout 2.0.0-beta3があるのですが、ConstraintHelperがうまく動いてくれませんでしたので、しぶしぶ今回はConstraintLayout 2.0.0-beta2を使用しています。。。
やはりベータ版だと、ところどころバグがあるので、stableになるまで待ったほうがよさそうですね

参考