はじめに
突然ですがみなさんはJetpack ComposeのModifierを自作したことはあるでしょうか。カスタムModifierを作る方法は大きく2つあります。
- 既存のModifierをラップする方法
- Modifier.NodeとModifierNodeElementを実装する方法
今回は後者の方法で背景を単色で塗りつぶすModifierを自作してみようと思います。
この記事を通してModifierの仕組みを理解し、より自由度の高いカスタムModifierを作るヒントになっていると幸いです。
カスタムModifierを作る流れ
以下の大きく3ステップに分かれます。
- Modifier.Nodeを実装する
- ModifierNodeElementを実装する
- ModifierのExtensionを実装する
1.がModifierの振る舞いを決めるメインの実装部分になります。2.は1.で作成したModifier.Node
のインスタンスを管理したりデバッグ用の情報を提供したりする部分になります。3.は既存のModifierと組み合わせて使えるようにする部分になります。
順番に作っていきましょう。
Modifier.Node を作る
Modifier.Nodeを作っていきます。今回は背景色を塗りつぶすという描画に関するModifierのためDrawModifierNodeを使用します。他にもModifierの役割に応じて様々なノードタイプが用意されており、例えばLayoutModifierNodeやPointerInputModifierNodeなどがあります。
背景を単色で塗りつぶすためにdrawRect()
を呼び出します。引数のsize
を省略すると領域いっぱいに描画されます。
またdrawRect()
を呼び出した後にdrawContent()
を呼び出すのを忘れないでください。これを呼び出さないと矩形が描画されるだけでModifierを適用したComposableの内容が描画されなくなるので注意です。
class BackgroundNode(var color: Color) : DrawModifierNode, Modifier.Node() {
override fun ContentDrawScope.draw() {
drawRect(color = color) // コンテンツの背後に矩形を描画
drawContent() // コンテンツを描画(忘れないで!)
}
}
ModifierNodeElementを実装する
ModifierNodeElement<BackgroundNode>()
を実装したクラスを作成します。必ず実装する必要のあるメソッドはfun create()
とfun update(BackgroundNode)
の2つです。
fun create()
はレイアウトに初めてModifierが適用されるときに呼ばれるもので、Modifier.Nodeのインスタンスを返せばOKです。
fun update()
はレイアウトの入力に変更があった際に呼ばれます。基本的には最新の状態が保たれるようにModifier.Nodeのインスタンスのパラメータを更新すればOKです。インスタンスをコピーして新しいインスタンスを作りたくなるところですが、パフォーマンスのために同じインスタンスを使い回すのが一般的なため、プロパティを書き換えられるように先ほど作ったBackgroundNode
のcolor
プロパティはvar
で宣言しています。
ちなみにModifierNodeElementのクラスにdata class
を使用しているのもパフォーマンスが理由で、equals()
とhashCode()
の自動生成を活用し同じインスタンスを再利用するためです。
InspectorInfo.inspectableProperties()
の実装は任意です。ここで設定した内容はAndroid StudioのLayout Inspectorで確認することができます。デバッグ時に便利なので実装しておくことをおすすめします。
data class BackgroundElement(val color: Color) : ModifierNodeElement<BackgroundNode>() {
override fun create(): BackgroundNode {
return BackgroundNode(color)
}
override fun update(node: BackgroundNode) {
node.color = color
}
override fun InspectorInfo.inspectableProperties() {
name = "Background"
properties["color"] = color
}
}
ModifierのExtensionを実装する
既存のModifierとチェーンして呼び出せるようにModifierのExtensionを定義します。infix関数のthenはModifierとModifierをチェーンします。
fun Modifier.backgroundColor(color: Color) = this then BackgroundElement(color)
使用する側はこんな感じです。
Text(
text = "Compose",
modifier = Modifier
.backgroundColor(Color.Yellow)
.padding(16.dp)
)
完成!
Modifierについてさらに詳しく
ModifierはJetpack Composeの中でも奥が深く、カスタマイズ性に富んだ面白い技術です。
もっと詳しく知りたい方は以下の資料などを参考にしてみてください