Compose LazyColumn:アイテムの制御(Change/Insert/Move/Remove)

投稿日:  更新日:

LazyColumnは「複数のデータやオブジェクト」または「コレクション」を一覧(リスト)表示するUIです。

単純に一覧表示できれば良い用途もありますが、表示に加えてアイテムの制御(変更/追加/移動/削除)を必要とする場面が多くあります。

最近の携帯端末はパソコンと同じ利便性をアプリに求めますから、「データの表示だけでなく編集もできる」という機能は必須の要望です。

ここでは、LazyColumnでアイテムの制御を行う方法を紹介します。

※環境:Android Studio Koala | 2024.1.1
    Kotlin 1.9.0
    Compose Compiler 1.5.1
    androidx.compose.foundation:foundation 1.6.8

スポンサーリンク

アイテムの制御

LazyColumnで作成した一覧表示に、次のようなアイテムの制御機能を組み込む場合を考えます。

  • (a)アイテムの変更(Change)
  • (b)アイテムの追加(Insert)
  • (c)アイテムの移動(Move)
  • (d)アイテムの削除(Remove)

ユーザへ制御の結果を知らせるためには、表示の更新が必要です。

表示の更新

スポンサーリンク

画面の描画

Jetpack Composeにおけるアプリ画面の描画は、3つのフェーズ(Composition/Layout/Drawing)からなる描画処理により行われます。

ただし、3つのフェーズが毎回実行されることは無く、表示(状態)に変更があったUI要素(Composable関数)の必要なフェーズのみを実行します。

フェーズのスキップの条件

これにより、アプリ画面の描画に必要なコストを、大幅に削減しています。

※詳細は「Jetpack Compose:Composeによるアプリ画面の描画」を参照
※詳細は「Jetpack Compose:再Composeとスキップ」を参照

スポンサーリンク

表示の更新

LazyColumnを使って一覧表示するだけであれば、以下のように記述するだけです。ただし、アイテムの変更はできません。

@Stable
data class NumData(val id: Int, var num: Int)

@Composable
fun SampleList(datas: List<NumData>) {
    LazyColumn {
        items(items = datas) {
            ItemBox { Text(text = "Item %02d".format(it.num)) }
        }
    }
}

アイテムの変更と表示の更新を行えるようにするには、次の処理が必要です。

  • (1)要素の変更が可能なコレクションを作成(List⇒MutableListへ変換)
  • (2)再Composeを超えて要素を保持(rememberでComposition内に格納)
  • (3)要素の変更を監視、変更で再Compose(mutableStateListOf)
  • (4)要素の変更を行う仕組み(Buttonなどのイベント処理)
※rememberについては「Jetpack Compose:再Composeを超えて状態の保持」を参照
※mutableStateOfについては「Jetpack Compose:再Composeのスケジューリング」を参照

例えば、「上から5番目にアイテムを追加」を実装すると、次のようになります。

@Stable
data class NumData(val id: Int, var num: Int)

private var ID: Int = 100
private fun newData() = try { NumData(ID, ID) } finally { ID++ }

@Composable
fun SampleList(datas: List<NumData>) {
    val _datas = remember {										// (2)
        mutableStateListOf<NumData>().apply { addAll(datas) }   // (1),(3)
    }

    LazyColumn {
        items(items = _datas) {
            ItemBox { Text(text = "Item %02d".format(it.num)) }
        }
    }

    Button(
        onClick = { _datas.add(4, newData()) }					// (4)
    ) { Text(text = "ins")}
}

Jetpack Composeは「状態の変更を検出して再Composeさせる」という、表示を更新するための御膳立てが難しいです。しかし、mutableStateListOfが行ってくれます。とっても便利です。

スポンサーリンク

更新の効率化

LazyColumnによる一覧表示は巨大なUIツリーです。「アイテムを構成するUI要素 × アイテム数」になるからです。

多数のUI要素(Composable関数)を一度に扱うため、重い処理になります。

「重い処理」は端末のシームレスな動作を阻害します。ですので、更新の効率化が必須です。

不要な再Compose

先に示したサンプル(「上から5番目にアイテムを追加」)は、図のような再Composeが行われます。

「上から5番目にアイテムを追加」の再Compose、キーなし

item04~08のアイテムは表示が変わっていないのに、再Composeが行われています。これは不要な再Composeです。

アイテムをポジションで識別(効率化前)

不要な再Composeが行われる原因は、LazyColumnがアイテムをポジション(並び順)で識別しているからです。

追加されたアイテムによるポジションのずれを、状態の変化として扱います。

  • 4番目のアイテムの状態が4→100に変化―>再Compose
  • 5番目のアイテムの状態が5→ 4に変化―>再Compose
  •       :
  • 9番目のアイテムの状態が9→ 8に変化―>再Compose

アイテムをポジションで識別

アイテムをキーで識別(効率化後)

アイテムにキーを登録することができます。

キーは同一LazyColumn内でアイテム毎にユニーク(重複は許されない)なオブジェクトです。IDのような数値で構いません。

@Stable
data class NumData(val id: Int, var num: Int)

private var ID: Int = 100
private fun newData() = try { NumData(ID, ID) } finally { ID++ }

@Composable
private fun SampleList(datas: List<NumData>) {
    val _datas = remember {										// (2)
        mutableStateListOf<NumData>().apply { addAll(datas) }   // (1),(3)
    }

    LazyColumn {
        items(items = _datas, key = { it.id }) {	 // NumData#idをキーにした
            ItemBox { Text(text = "Item %02d".format(it.num)) }
        }
    }

    Button(
        onClick = { _datas.add(4, newData()) }					// (4)
    ) { Text(text = "ins")}
}

キーを登録すると、LazyColumnがアイテムをキーで識別するようになります。

追加されたアイテムがあったとしても、「Key:4」により識別されるアイテムの状態は変わりません。

アイテムをキーで識別

状態の変化が無くなるので再Composeは行われませんが、位置が変化するのでLayoutフェーズで配置が変わります。

「上から5番目にアイテムを追加」の再Compose、キーあり

スポンサーリンク

制御の例

以下はアイテムの制御の例です。

@Stable
data class NumData(val id: Int, var num: Int)

private var ID: Int = 100
private fun newData() = try { NumData(ID, ID) } finally { ID++ }

@Composable
private fun ItemCtrl(datas: List<NumData>) {
    val _datas = remember {
        mutableStateListOf<NumData>().apply { addAll(datas) }
    }
    Column(modifier = Modifier.fillMaxSize().padding(2.dp)) {
        Row {
            Button(modifier = Modifier.weight(1.0f),
                onClick = { _datas.set(4, newData()) }
            ) { Text(text = "chg")}
            Button(modifier = Modifier.weight(1.0f),
                onClick = { _datas.removeAt(4) }
            ) { Text(text = "rmv")}
            Button(modifier = Modifier.weight(1.0f),
                onClick = { _datas.add(4, newData()) }
            ) { Text(text = "ins")}
            Button(modifier = Modifier.weight(1.0f),
                onClick = {
                    val _temp = _datas.get(4)
                    _datas.removeAt(4)
                    _datas.add(2, _temp)
                }
            ) { Text(text = "mov")}
        }

        LazyColumn {
            items(items = _datas, key = { it.id }) {
                ItemBox { Text(text = "Item %02d".format(it.num)) }
            }
        }
    }
}

Change(変更)

5番目のアイテムを新しいアイテムに変更します。

Insert(追加)

5番目に新しいアイテムを追加します。

Move(移動)

5番目のアイテムを3番目に移動します。

Remove(削除)

5番目のアイテムを削除します。

スポンサーリンク

関連記事:

Jetpack composeは、アプリ開発に必要な一通りのUIコンポーネントをライブラリで提供しています。 そのライブラリ中のLazyColumnについて、構成や使用方法などをまとめます。 ※環境:Android Studio Koala | 2024.1.1     Kotlin 1.9.0     Compose Compiler 1.5.1     androidx.compose.foundation:foundation 1.6.8 ...
LazyColumnはLazyListScope DSLコマンドを使ってアイテムの並びを記述します。 コマンドは7つあり、各々について例を示します。 ※環境:Android Studio Koala | 2024.1.1     Kotlin 1.9.0     Compose Compiler 1.5.1     androidx.compose.foundation:foundation 1.6.8 ...
LazyColumnとColumnはどちらも一覧(リスト)表示するUIです。 記述方法は違いますが、スクロールしたり、アイテムを制御したり、ほぼ同じことが出来ます。 ただし、両者の特徴により、向き不向きがあります。よく違いを理解して、適材適所で使い分けが必要です。 ここでは、両者の違いをまとめました。 ※環境:Android Studio Koala | 2024.1.1     Kotlin 1.9.0     Compose Compiler 1.5.1     androidx.compose.foundation:foundation 1.6.8 ...
ある成果物の開発を進めるに時に、「先に問題へ対応する」と「後で問題へ対応する」では、後者の方が難易度は高いです。 開発は進むにつれて周辺との因果関係が複雑化ます。全体に影響を与えない形で問題へ対応することが、難しくなるからです。 不要な再Composeを回避する対応も同じだと思います。 ですので、発生源が明確で不要な再Composeは、コーディングの段階で潰しておくべきです。 ただし、不要な再Composeの発生源を知らなければ出来ません。 ここで紹介するList型入力は、不要な再Composeの発生源の一つです。 ※環境:Android Studio Koala | 2024.1.1     Kotlin 1.9.0     Compose Compiler 1.5.1     androidx.compose.foundation:foundation 1.6.8     kotlinx.collections.immutable 0.3.7 ...
ある成果物の開発を進めるに時に、「先に問題へ対応する」と「後で問題へ対応する」では、後者の方が難易度は高いです。 開発は進むにつれて周辺との因果関係が複雑化ます。全体に影響を与えない形で問題へ対応することが、難しくなるからです。 不要な再Composeを回避する対応も同じだと思います。 ですので、発生源が明確で不要な再Composeは、コーディングの段階で潰しておくべきです。 ただし、不要な再Composeの発生源を知らなければ出来ません。 ここで紹介するListStateは、不要な再Composeの発生源の一つです。 ※環境:Android Studio Koala | 2024.1.1     Kotlin 1.9.0     Compose Compiler 1.5.1     androidx.compose.foundation:foundation 1.6.8 ...
Jetpack composeは、アプリ開発に必要な一通りのUIコンポーネントをライブラリで提供しています。 そのライブラリ中のLazyRowについて、構成や使用方法などをまとめます。 ※環境:Android Studio Koala | 2024.1.1 Patch 1     Kotlin 1.9.0     Compose Compiler 1.5.1     androidx.compose.foundation:foundation 1.6.8 ...
スポンサーリンク