本記事は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を指定した方向に並べます。方向としては、HorizontalとVerticalが存在し、それぞれの並び方を以下に示します。こちらは最近リリースされた機能になるので、初めての方も多いと思います。
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`
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="BackgroundHelper">
<attr name="backgroundColor" format="color" />
</declare-styleable>
</resources>
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
せずに単純にmIds
のforEach
だけでも良いのでは? と思っているのですが、他のGroup
やBarrier
でも同じ手法で実装されているので、毎回これを使っています。
実行
この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になるまで待ったほうがよさそうですね