【Jetpack Compose】Modifierを自作する

## はじめに

突然ですがみなさんはJetpack ComposeのModifierを自作したことはあるでしょうか。カスタムModifierを作る方法は大きく2つあります。

1. 既存のModifierをラップする方法
1. [Modifier.Node](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/Modifier.kt;l=184?hl=ja)と[ModifierNodeElement](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifierNodeElement.kt;l=39?hl=ja)を実装する方法

今回は後者の方法で背景を単色で塗りつぶすModifierを自作してみようと思います。

この記事を通してModifierの仕組みを理解し、より自由度の高いカスタムModifierを作るヒントになっていると幸いです。

## カスタムModifierを作る流れ

以下の大きく3ステップに分かれます。

1. [Modifier.Node](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/Modifier.kt;l=184?hl=ja)を実装する
1. [ModifierNodeElement](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifierNodeElement.kt;l=39?hl=ja)を実装する
1. ModifierのExtensionを実装する


1.がModifierの振る舞いを決めるメインの実装部分になります。2.は1.で作成した`Modifier.Node`のインスタンスを管理したりデバッグ用の情報を提供したりする部分になります。3.は既存のModifierと組み合わせて使えるようにする部分になります。

順番に作っていきましょう。

## Modifier.Node を作る

Modifier.Nodeを作っていきます。今回は背景色を塗りつぶすという描画に関するModifierのため[DrawModifierNode](https://developer.android.com/reference/kotlin/androidx/compose/ui/node/DrawModifierNode)を使用します。他にもModifierの役割に応じて様々なノードタイプが用意されており、例えばLayoutModifierNodeやPointerInputModifierNodeなどがあります。

背景を単色で塗りつぶすために`drawRect()`を呼び出します。引数の`size`を省略すると領域いっぱいに描画されます。

また`drawRect()`を呼び出した後に`drawContent()`を呼び出すのを忘れないでください。これを呼び出さないと矩形が描画されるだけでModifierを適用したComposableの内容が描画されなくなるので注意です。

```kotlin
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で確認することができます。デバッグ時に便利なので実装しておくことをおすすめします。

```kotlin
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
    }
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/56635/13ade42f-f17c-12de-0144-6773cce0ab6a.png)


## ModifierのExtensionを実装する

既存のModifierとチェーンして呼び出せるようにModifierのExtensionを定義します。infix関数のthenはModifierとModifierをチェーンします。

```kotlin
fun Modifier.backgroundColor(color: Color) = this then BackgroundElement(color)
```

使用する側はこんな感じです。

```kotlin
Text(
    text = "Compose",
    modifier = Modifier
        .backgroundColor(Color.Yellow)
        .padding(16.dp)
)
```

完成！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/56635/958f19c5-98ef-5769-28bf-59ec86473d05.png)



## Modifierについてさらに詳しく

ModifierはJetpack Composeの中でも奥が深く、カスタマイズ性に富んだ面白い技術です。
もっと詳しく知りたい方は以下の資料などを参考にしてみてください :thumbsup: 

https://developer.android.com/develop/ui/compose/custom-modifiers?hl=ja

<iframe width="300" height="270" src="https://www.youtube.com/embed/BjGX2RftXsU?si=Ac8GcMfUdr0b_wzN" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>


<iframe width="480" height="270" src="https://www.youtube.com/embed/W9rC9N9ql1o?si=W81j9ed-qh6qtg-T" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>


https://speakerdeck.com/gya/lets-modifier-dot-node