この記事では前回の「基本編」で作成したプロジェクトを元に、より「リスト要素」として使いやすい機能を追加することを目標としています。
この記事で解説しているプロジェクトは以下のリポジトリになります。
- リスト要素のタップ処理
- リスト要素の複数選択機能
最終的に今回の機能を実装することで以下のような動きができるアプリを作成します。
完成しているコードから対象の部分を抜き出して解説していきます。
コード全体を見ることでより理解が進むと思うので、「はじめに」に貼ってあるリポジトリも参考にしてみてください。
UIを追加
このコードで行っているのは、以下の3つのUIの追加です。
- アプリバーの追加
- タイトルの追加
- 要素の複数選択機能の有効無効を切り替えるためのボタンを追加
- 複数要素を表示したときに画面下部に表示する「選択された項目数」を表示して、押されたら「選択された要素をトーストで出力」するボタンを追加
それに加えて、関数の最初に「複数選択状態」を保持する変数enableMultiselectFlag
を定義しています。
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ScrollListScreen(modifier: Modifier = Modifier) {
// 複数選択機能の有効フラグ
var enableMultiselectFlag by remember { mutableStateOf(false) }
// === 省略 ===
Scaffold(
// 画面上部のアプリバーを追加
topBar = {
TopAppBar(
colors = topAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
titleContentColor = MaterialTheme.colorScheme.primary,
),
// タイトルを追加
title = {
Row(
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxSize()
) {
Text(text = "LazyColumnデモ")
}
},
actions = {
// 複数選択機能の切り替えボタンを追加
Button(onClick = { enableMultiselectFlag = !enableMultiselectFlag }) {
Text(text = if (enableMultiselectFlag) "選択解除" else "複数選択")
// 選択解除されたら要素のフラグをすべてfalseに設定
if (!enableMultiselectFlag) {
for (data in selectedData) {
data.selected.value = false
}
}
}
}
)
},
floatingActionButton = {
if (!enableMultiselectFlag) {
return@Scaffold
}
if (selectedData.isEmpty()) {
return@Scaffold
}
// 画面下部にあるボタンを追加
FloatingActionButton(
onClick = {
var showText = ""
for (data in selectedData) {
showText += data.name+","
}
// トーストを表示
Toast
.makeText(context, showText, Toast.LENGTH_SHORT)
.show()
},
modifier = Modifier.width(100.dp)
) {
// 選択されている要素数を表示
Text(text = "${selectedData.count()}件選択")
}
},
// === 省略 ===
}
リスト要素のタップ処理
リスト内の要素を選択できるようにするには、clickable
属性を付与することでタップした場合のイベントを取得できるようになります。
今回は、Row
に対してclickable
属性を付与することで、対象要素の列のどこを選択してもタップしたイベントを取得できるようにしました。
この方法は「タップ時のアクションが1つだけ」の場合は、ユーザーが選択しやすいのでいいですが、「複数のアクションを1つの要素」が持っている場合は誤作動の原因になるので、避けたほうがいいと思います。
@SuppressLint("ReturnFromAwaitPointerEventScope")
@Composable
fun ScrollView(
testData: List<SelectData>,
enableMultiselect: Boolean,
modifier: Modifier
) {
// === 省略 ===
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
// === 省略 ===
.clickable {
// 複数選択の場合
if (enableMultiselect) {
// === 省略 ===
}else{
// 単体選択の場合
Toast
.makeText(context, "on click ${data.name}", Toast.LENGTH_SHORT)
.show()
}
}
// === 省略 ===
また、複数選択の機能も.clickable
属性内で実装しています。
詳細は次の節で解説します。
リスト要素の複数選択機能
リスト要素は、前回の基本編では「名前だけの管理」でしたが、今回から、SelectData
というDataCalssを作成して管理しています。
このようにしたのは、選択状態を各要素が持つ必要があったためです。
data class SelectData(
val name: String,
var selected: MutableState<Boolean>,
)
また、「選択状態を保持」する必要があるので、各要素の生成ロジックを以下のようにmutableStateListOf
を使うことで状態をビューが更新されても保てるようにしています。
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ScrollListScreen(modifier: Modifier = Modifier) {
// === 省略 ===
// 表示用のデータを生成
val testData = remember {
mutableStateListOf<SelectData>().apply {
repeat(100) {
add(
SelectData(
"データ項目:$it",
mutableStateOf(false)
)
)
}
}
}
// === 省略 ===
前の節で単体選択時の処理を書いた、clickable
属性内で、複数選択状態が有効かをチェックして有効な場合は、選択状態を反転させて状態を更新しています。
さらに、その後にreturn@clickable
を書くことで、それ以降の処理を動かないようにブロックしています。
(現状のコードでは効果がない)
.clickable {
// 複数選択の場合
if (enableMultiselect) {
data.selected.value = !data.selected.value
return@clickable
} else {
// 単体選択の場合
// === 省略 ===
}
}
今回追加した機能は「LazyColumn」を使ったというよりは、リストUIの機能追加の一例をご紹介する形になりました。
「LazyColumn」自体に「要素へのジャンプ機能」や「アニメーション機能」などのオプションが備わっています。
よりリッチな表現をしたい方は以下のリンクから公式のドキュメントを参考に自分好みにカスタマイズしてみてください。