アプリ開発備忘録

PlayStationMobile、Android、UWPの開発備忘録

【Jetpack Compose】新Modifierシステムの利点と書き方

変更点

Modifierのパフォーマンスが向上したのは以下の開発者ブログの通りです。
https://android-developers.googleblog.com/2023/08/whats-new-in-jetpack-compose-august-23-release.html

Modifierには Modifier.composed {} という、ステートフルなModifierを実装する事ができる機能があります。これはComposableな関数を引数に取るため、前回と同じModifierの状態になるかは、処理を走らせてからでないと比較する事ができませんでした。
ステートフルなModifierをComposableな関数を使わずに、単純に比較できるようにしたというのが今回の変更です。

実装してみる

今回は以下の、サイズはそのままに、表示状態だけ切り替えるvisibility機能を作成してみます。
Compose UIは1.4系を使用しています。

public fun Modifier.visibility(
    isVisible: Boolean,
): Modifier

Modifier.Node

まずはModifier.Nodeを継承します。今まではModifier.Elementが直接使用されていました。
描画周りを変更したいので、 DrawModifierNode を実装します。
mutableな変数を持ち、それを使用するコードを書きます。

@OptIn(ExperimentalComposeUiApi::class)
private class VisibilityModifierNode(
    var isVisible: Boolean,
) : Modifier.Node(), DrawModifierNode {
    override fun ContentDrawScope.draw() {
        if (isVisible) {
            drawContent()
        }
    }
}

ModifierNodeElement

続いて、ModifierNodeElementを継承したクラスを作成します。ModifierNodeElementModifier.Element を継承しています。
そして、ModifierNodeElementで重要な部分は以下です。hashCodeequalsを必ず実装するようにコメントがあります。これによってステートフルでも単純に比較できるModifierが作成されるわけです。data classを使えばhashとequalsは自動で作成されるので、それで十分です。

    // Require hashCode() to be implemented. Using a data class is sufficient. Singletons and
    // modifiers with no parameters may implement this function by returning an arbitrary constant.
    abstract override fun hashCode(): Int

    // Require equals() to be implemented. Using a data class is sufficient. Singletons may
    // implement this function with referential equality (`this === other`). Modifiers with no
    // inputs may implement this function by checking the type of the other object.
    abstract override fun equals(other: Any?): Boolean

以下のようにcreateupdateを実装します。

@OptIn(ExperimentalComposeUiApi::class)
private data class VisibilityModifierNodeElement(
    private val isVisible: Boolean,
) : ModifierNodeElement<VisibilityModifierNode>() {
    override fun InspectorInfo.inspectableProperties() {
        name = "isVisible"
        properties["isVisible"] = isVisible
    }

    override fun create(): VisibilityModifierNode {
        return VisibilityModifierNode(isVisible = isVisible)
    }

    override fun update(node: VisibilityModifierNode): VisibilityModifierNode { // 1.5系では戻り値が無くなっている
        node.isVisible = isVisible
        return node
    }
}

Modifier

これで比較可能でステートフルなModifier.Elementが作成できました。
後はModifierの拡張関数として定義してあげれば良いです。サンプルコード見ていて気が付きましたが、thenって中置演算子だったんですね。

public fun Modifier.visibility(
    isVisible: Boolean,
): Modifier = this then VisibilityModifierNodeElement(isVisible = isVisible)

CompositionLocalを使用する

CompositionLocalを参照したい場合は以下にサンプルコードがあります。1.5系が必要です。
androidx.compose.ui.node.CompositionLocalConsumerModifierNode を継承し、currentValueOfで取り出せます。
https://cs.android.com/androidx/platform/tools/dokka-devsite-plugin/+/4fb986840db2b2383d29be0aa982f2e08bcc3d88:testData/compose/samples/ui/samples/ModifierCompositionLocalSample.kt;bpv=0