---
title: "Jetpack ComposeでカスタムModifierを作成するためのModifier.Nodeの活用方法"
description: "この記事では、Modifier.then()、Modifier.composed()、そして Modifier.Node の3つの主要なAPIを使用して、カスタムModifierを作成する方法を学びます。"
language: "jp"
publishedAt: "2025-10-27T08:08:40Z"
updatedAt: "2025-10-27T08:08:40Z"
authors:
  - name: "Jaewoong Eum"
    url: "https://www.revenuecat.com/jp/blog/author/jaewoong-eum"
category: "[ エンジニアリング ]"
categoryUrl: "https://www.revenuecat.com/jp/blog/engineering"
readingTime: 1
canonical: "https://www.revenuecat.com/jp/blog/engineering/compose-custom-modifier"
---

# Jetpack ComposeでカスタムModifierを作成するためのModifier.Nodeの活用方法

この記事では、Modifier.then()、Modifier.composed()、そして Modifier.Node の3つの主要なAPIを使用して、カスタムModifierを作成する方法を学びます。

## Table of contents

- [Modifier.then](#modifierthen)
- [Modifier.composed](#modifiercomposed)
- [Modifier.Node：状態とロジックの中核](#modifiernode)
  - [Modifier.Node](#modifiernode-2)
- [まとめ](#section)

現代のAndroid開発において、Jetpack ComposeはAndroid向けUI構築のためのモダンなツールキットであるだけでなく、Kotlin Multiplatformプロジェクトもサポートする存在へと進化しています。そのエコシステムは急速に拡大しており、いまやAndroid開発に欠かせない要素となっています。

Composeの中心にあるのが `Modifier`Modifierは単なるスタイルやテーマを適用する仕組みではなく、コンポーネント同士を独立させつつ、柔軟にカスタマイズできるようにするデザインパターンです。カスタムModifierを作成することで、一度定義した動作をアプリ全体で再利用でき、UIコードをよりクリーンかつ柔軟に保つことができます。

この記事では、 `Modifier.then()`、 `Modifier.composed()`、そして `Modifier.Node`、という3つの主要なAPIを使ってカスタムModifierを作成する方法を紹介し、さらに [RevenueCat’s Android SDK](https://github.com/RevenueCat/purchases-android) が `Modifier.Node` を活用してUIパフォーマンスを向上させている仕組みについて解説します。

## Modifier.then

`Modifier.then()` 関数を使うのは、カスタムModifierを作成する最も簡単な方法です。この関数を使うと、複数のModifierをチェーンでつなぎ、最終的な結果として結合されたModifierを返すことができます。以下の例のように使用します。

```kotlin
@Composable
fun Modifier.composableModifier(): Modifier {
    val color = LocalContentColor.current.copy(alpha = 0.5f)
    return this then Modifier.background(color)
}

@Composable
fun MyComposable() {
    val composedModifier = Modifier.composableModifier() \/\/ Cannot be extracted any higher
}
```

`infix fun Modifier.then(other: Modifier)` は、Jetpack ComposeにおけるModifierのチェーン処理を担う基本的な演算子です。この関数は単なるフラットなリストを作るのではなく、 `CombinedModifier`. と呼ばれる再帰的な2要素のデータ構造を生成します。この構造は、Compose UIツールキットが必要とする特定の方向性をもった走査パターンに最適化された、連結リスト（consリスト）のような仕組みとして機能します。

例えば、UI要素のプロパティを構築するために、開発者が宣言的にModifierをチェーンできるようにするシンプルなModifierがあるとしましょう。

```kotlin
Modifier
    .padding(16.dp)      \/\/ First (outer)
    .background(Color.Blue) \/\/ Second
    .clickable { }       \/\/ Third (inner)
```

多くの人は、これで `List<Modifier.Element>`が作られると想像するかもしれません。しかし実際にはそうではありません。チェーンの各Modifierが暗黙に呼び出す `Modifier.then()` は、入れ子の木構造を組み立てます。この設計は、パフォーマンスと正確性のために意図的に選ばれた重要なアーキテクチャ上の判断です。

`Modifier.then()` 関数の内部コードを確認すると、この関数が内部的に新しい `CombinedModifier` インスタンスを生成していることがわかります。

```kotlin

infix fun then(other: Modifier): Modifier =
    if (other === Modifier) this else CombinedModifier(this, other)
        
 class CombinedModifier(internal val outer: Modifier, internal val inner: Modifier) : Modifier {
    override fun <R> foldIn(initial: R, operation: (R, Modifier.Element) -> R): R =
        inner.foldIn(outer.foldIn(initial, operation), operation)

    override fun <R> foldOut(initial: R, operation: (Modifier.Element, R) -> R): R =
        outer.foldOut(inner.foldOut(initial, operation), operation)

    override fun any(predicate: (Modifier.Element) -> Boolean): Boolean =
        outer.any(predicate) || inner.any(predicate)

    override fun all(predicate: (Modifier.Element) -> Boolean): Boolean =
        outer.all(predicate) && inner.all(predicate)

    override fun equals(other: Any?): Boolean =
        other is CombinedModifier && outer == other.outer && inner == other.inner

    override fun hashCode(): Int = outer.hashCode() + 31 * inner.hashCode()

    override fun toString() =
        "[" +
            foldIn("") { acc, element ->
                if (acc.isEmpty()) element.toString() else "$acc, $element"
            } +
            "]"
}
```

この関数の目的は、既存のチェーン (`this`) と新しいModifier (`other`), and join them using theという2つのModifierを受け取り、それらを `CombinedModifier` クラスを使って結合することです。 `CombinedModifier` はコレクションではなく、単純な2要素のコンテナであり、リンクドリストのノードとして機能します。

```kotlin
class CombinedModifier(internal val outer: Modifier, internal val inner: Modifier) : Modifier
```

- `outer`: チェーン内で*先に*追加されたModifierを保持します。
- `inner`: 新たに追加されたModifierを保持します。
たとえば、 `Modifier.padding(8.dp).background(Color.Red)`と書くと、生成される構造は次のようになります：`CombinedModifier(outer = CombinedModifier(outer = padding, inner = background), inner = clickable)`

これは、左結合（left-associative）の再帰的な構造を作り出しており、 `((padding then background) then clickable)`のような形になります。

この「リンクドリスト」構造こそが、Modifierがどのように処理されるかを理解する鍵です。 `CombinedModifier` の仕組みは、その双方向の走査メソッドによって実現されています。通常のリストが「先頭から末尾」へ1方向にしか走査できないのに対し、`CombinedModifier` はUIパイプラインの異なるフェーズに対応するため、逆方向にも走査可能です。

つまり、 `Modifier.then` は、シンプルでありながら最適化された関数であり、宣言的かつチェーン可能なModifierシステム全体を支える中核的な仕組みとなっています。

## Modifier.composed

もうひとつ利用できるAPIが `Modifier.composed()`です。この関数を使うと、ラムダ式内で Composable関数 を利用できるカスタムModifierを作成できます。

```kotlin
fun Modifier.myStatefulModifier() = composed {
    val myState by remember { mutableStateOf(0f) }
    \/\/ ... use myState
    Modifier.drawBehind { \/* ... *\/ }
}
```

内部的な動作は `Modifier.then` とよく似ています。 `ComposedModifier` は新しい ComposedModifier インスタンスを生成し、それを既存のModifierと `Modifier.then` を使って結合します。以下の例のように動作します。

```kotlin
fun Modifier.composed(
    inspectorInfo: InspectorInfo.() -> Unit = NoInspectorInfo,
    factory: @Composable Modifier.() -> Modifier,
): Modifier = this.then(ComposedModifier(inspectorInfo, factory))
```

`Modifier.composed` はカスタムModifierを簡単に作成できる便利なAPIですが、パフォーマンス面での問題があるため、**現在は推奨されていません**。このメソッドでは、`remember` などの `@Composable` 関数 をModifierインスタンス作成時に呼び出すことができます。これにより、特定インスタンス専用の状態（state）を保持でき、同じModifierオブジェクトを複数箇所で安全に再利用できるという利点があります。

しかし、実際に `@Composable` 関数を呼ばずに `Modifier.composed` を使用すると、不要なオーバーヘッドが発生します。
この場合、Modifierは*スキップ可能（skippable）*と見なされなくなり、再コンポジション時に追加の処理が強制されるため、毎回Modifierチェーン全体を再実行して状態を更新する必要が生じます。 結果として、UIの再コンポジション時のパフォーマンスが低下する原因となります。

## Modifier.Node：状態とロジックの中核

`Modifier.Node` は Compose UI 1.3.0 から導入されたAPIで、現在では `Modifier.composed`に代わるカスタムModifier作成の推奨手段となっています。従来の `Modifier.composed` は、状態を持つカスタムModifierを作成する初期の仕組みとして提供されていましたが、各Modifierインスタンスごとに新しいサブコンポジションを生成する必要があるという大きなパフォーマンスコストがありました。このオーバーヘッドは、特にリストなどの動的UIにおいてパフォーマンス低下を引き起こす原因となっていました。

この課題を直接解決するために登場したのが `Modifier.Node` です。これはComposeのレンダリングパイプラインに統合された軽量でライフサイクル管理に対応した低レベルAPIであり、サブコンポジションのオーバーヘッドなしに状態を持つModifierを作成できるようになっています。

### Modifier.Node

`Modifier.Node` の実装は、カスタムModifierの中核部分となります。
これは状態を持つ可変クラスであり、Modifierのプロパティを保持し、その動作ロジックを実装します。重要なのは、 `Modifier.Node` のインスタンスが複数回の再コンポジションをまたいで保持されること、そして設定が変わらない場合には再利用も可能であるという点です。

```kotlin
\/\/ The Node: Holds state (color) and logic (drawCircle)
private class CircleNode(var color: Color) : DrawModifierNode, Modifier.Node() {
    override fun ContentDrawScope.draw() {
        \/\/ Implements the drawing logic
        drawCircle(color)
    }
}
```

`Element` には主に次の2つの役割があります：

1. create()：Modifierが最初に適用されたときに、 `Modifier.Node` のインスタンスを生成します。

2. update(node: CircleNode)：これがパフォーマンスの鍵です。再コンポジション時に `Element` が変更された（ `equals` メソッドによって判定される）場合、既存の `Node` インスタンスに対してこの関数が呼び出されます。これにより、新しいオブジェクトを生成せずに `Node` の状態を更新できます。

**補足：** `ModifierNodeElement` では、 `equals` と `hashCode` を正しく実装することが非常に重要です。
最も簡単な方法は data class を使用することです。これらを正しく実装しないと、 `update` が不要に呼ばれ、パフォーマンス上の利点が失われてしまいます。



Modifier ファクトリー



これは、開発者が利用するためのシンプルで明確な公開API関数です。この関数では、 `then` 演算子を使って、新しい `ModifierNodeElement` を既存のModifierチェーンに連結します。

```kotlin
\/\/ The Factory: The clean public API
fun Modifier.circle(color: Color): Modifier = this.then(CircleElement(color))
```

これで、作成したカスタムModifierは、他の組み込みModifier関数と同じように利用できるようになります。ステートレスなファクトリー（`Element`）とステートフルなワーカー（`Node`）を分離することで、オブジェクトの生成を最小限に抑え、再利用性を最大化します。さらに、それが持つ豊富なノードタイプとライフサイクル対応APIによって、UIパイプラインのあらゆるフェーズに直接的かつ安全で効率的なアクセスが可能になります。

[RevenueCat’s Android SDK](https://www.revenuecat.com/docs/getting-started/installation/android) でも、より高いUIパフォーマンスを実現するために `Modifier.Node` を活用しています。最近、プレースホルダー機能の実装において、従来の Modifier.composed() から Modifier.Node へ移行しました。詳細や実際のコード例は、[Placeholder.kt file](https://github.com/RevenueCat/purchases-android/blob/main/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/composables/Placeholder.kt) で確認できます。

## まとめ

この記事では、 `Modifier.then()`、 `Modifier.composed()`、そして `Modifier.Node` の3つの主要APIを使ってカスタムModifierを作成する方法を紹介し、さらに [RevenueCat SDK](https://www.revenuecat.com/docs/getting-started/installation/android) が `Modifier.Node` を活用してUIパフォーマンスを向上させている仕組みを解説しました。それぞれのアプローチにはトレードオフがあります。 `Modifier.then()` シンプルで軽量ですが、 `Modifier.composed()` は誤用するとパフォーマンス低下を招く可能性があります。一方、 `Modifier.Node` はよりモダンで効率的な方法を提供し、柔軟かつパフォーマンスに優れたカスタム動作を実現できます。これらのパターンを正しく理解し、意図的に使い分けることで、再利用性が高く、表現力があり、パフォーマンスにも優れたCompose UIを構築することができるでしょう。

いつものように、ハッピーコーディングを！

— [Jaewoong](https://github.com/skydoves)
