タスクを編集できるようにする

タスクを編集できるようにするには、以下のことを行います。
- 編集モードの管理
どのタスクが現在編集モードにあるかを管理するための状態を導入します。 - 編集用の TextField を表示する
タスクが編集モードの場合、そのタスクの表示テキストを TextField に置き換えます。 - 編集を保存するボタン(またはアイコン)を追加する
編集モードの TextField の横に、変更を確定するためのボタン(またはアイコン)を追加します。 - 編集をキャンセルするボタン(またはアイコン)を追加する
編集モードを終了し、変更を破棄するためのボタン(またはアイコン)を追加します。
具体的な手順
TodoListScreen 関数を編集していきます。
1. 必要なimportを追加
ファイルの先頭あたりに、以下を追加してください。(既にあるものもあるかもしれません)
import androidx.compose.material.icons.filled.Edit // 編集アイコン用
import androidx.compose.material.icons.filled.Done // 完了アイコン用
import androidx.compose.material.icons.filled.Close // キャンセルアイコン用
import androidx.compose.material3.BasicTextField // 編集モードで使うシンプルなTextField
2. TodoListScreen を修正して編集機能を実装する
TodoListScreen 関数の中の LazyColumn の items ブロックを中心に変更します。
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TodoListScreen(navController: NavController) {
var todoItems by rememberSaveable { mutableStateOf(mutableListOf<TodoItem>()) }
var newTodoText by rememberSaveable(stateSaver = TextFieldValue.Saver) {
mutableStateOf(TextFieldValue(""))
}
var expanded by remember { mutableStateOf(false) }
var selectedCategory by remember { mutableStateOf(TodoCategory.NONE) }
// 現在編集中のタスクのIDを保持する状態変数 (nullの場合は編集中のタスクなし)
var editingItemId by remember { mutableStateOf<Long?>(null) }
// 編集中のタスクのテキストを保持する状態変数
var editingText by rememberSaveable(stateSaver = TextFieldValue.Saver) {
mutableStateOf(TextFieldValue(""))
}
// 編集中のタスクのカテゴリを保持する状態変数
var editingCategory by remember { mutableStateOf(TodoCategory.NONE) }
// 編集中のカテゴリ選択ドロップダウンの状態
var editingCategoryExpanded by remember { mutableStateOf(false) }
Column(
modifier = Modifier.fillMaxSize().padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "ToDoリスト",
fontSize = 24.sp,
modifier = Modifier.padding(bottom = 16.dp)
)
OutlinedTextField(
value = newTodoText,
onValueChange = { newValue -> newTodoText = newValue },
label = { Text("新しいToDo") },
singleLine = true,
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 8.dp)
)
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = { expanded = !expanded },
modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp)
) {
OutlinedTextField(
value = selectedCategory.displayName,
onValueChange = {},
readOnly = true,
label = { Text("カテゴリを選択") },
trailingIcon = { Icon(Icons.Filled.ArrowDropDown, "ドロップダウン") },
modifier = Modifier.menuAnchor().fillMaxWidth()
)
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
TodoCategory.values().forEach { category ->
DropdownMenuItem(
text = { Text(category.displayName) },
onClick = {
selectedCategory = category
expanded = false
}
)
}
}
}
Button(
onClick = {
if (newTodoText.text.isNotBlank()) {
val newTodo = TodoItem(
text = newTodoText.text,
category = selectedCategory
)
todoItems = (todoItems + newTodo).toMutableList()
newTodoText = TextFieldValue("")
selectedCategory = TodoCategory.NONE
}
},
modifier = Modifier.fillMaxWidth().padding(bottom = 16.dp)
) {
Text("追加")
}
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
items(todoItems, key = { it.id }) { item ->
Card(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp),
border = BorderStroke(1.dp, Color.LightGray)
) {
// 編集中のアイテムかどうかで表示を切り替える
if (editingItemId == item.id) {
// 編集モードの場合
Row(
modifier = Modifier
.fillMaxWidth()
.background(editingCategory.color) // 編集中のカテゴリの色を反映
.padding(horizontal = 16.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
// 編集用TextField
BasicTextField( // BasicTextFieldは枠線がないシンプルなTextField
value = editingText,
onValueChange = { editingText = it },
singleLine = true,
modifier = Modifier
.weight(1f)
.padding(end = 8.dp)
)
// 編集用カテゴリ選択ドロップダウン
ExposedDropdownMenuBox(
expanded = editingCategoryExpanded,
onExpandedChange = { editingCategoryExpanded = !editingCategoryExpanded }
) {
Text(
text = editingCategory.displayName,
modifier = Modifier
.menuAnchor()
.padding(horizontal = 4.dp)
.background(Color.LightGray) // 選択中のカテゴリを少し目立たせる
.clickable { editingCategoryExpanded = true }
)
ExposedDropdownMenu(
expanded = editingCategoryExpanded,
onDismissRequest = { editingCategoryExpanded = false }
) {
TodoCategory.values().forEach { category ->
DropdownMenuItem(
text = { Text(category.displayName) },
onClick = {
editingCategory = category
editingCategoryExpanded = false
}
)
}
}
}
// 編集完了ボタン
IconButton(onClick = {
todoItems = todoItems.map { todo ->
if (todo.id == item.id) {
todo.copy(text = editingText.text, category = editingCategory)
} else {
todo
}
}.toMutableList()
editingItemId = null // 編集モード終了
}) {
Icon(Icons.Filled.Done, contentDescription = "編集を保存")
}
// 編集キャンセルボタン
IconButton(onClick = {
editingItemId = null // 編集モード終了
}) {
Icon(Icons.Filled.Close, contentDescription = "編集をキャンセル")
}
}
} else {
// 通常表示モードの場合
Row(
modifier = Modifier
.fillMaxWidth()
.background(item.category.color)
.padding(horizontal = 16.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = "[${item.category.displayName}] ${item.text}",
modifier = Modifier
.weight(1f)
.padding(end = 8.dp)
)
// 編集ボタン
IconButton(onClick = {
editingItemId = item.id // 編集モード開始
editingText = TextFieldValue(item.text) // 編集テキストを初期化
editingCategory = item.category // 編集カテゴリを初期化
}) {
Icon(Icons.Filled.Edit, contentDescription = "編集")
}
// 削除ボタン
IconButton(onClick = {
todoItems = todoItems.filter { it.id != item.id }.toMutableList()
}) {
Icon(Icons.Filled.Delete, contentDescription = "削除")
}
}
}
}
}
}
Button(
onClick = { navController.popBackStack() },
modifier = Modifier.padding(top = 16.dp)
) {
Text("戻る")
}
}
}
- editingItemId: 現在編集中の TodoItem の id を保持します。null の場合、どの項目も編集されていません。
- editingText と editingCategory: 編集中の TextField の値とカテゴリ選択の状態を保持します。
- if (editingItemId == item.id) { … } else { … }: 各 TodoItem が現在編集中のものかどうかを editingItemId と比較して、表示するUIを切り替えます。
- 編集モードのUI:
- BasicTextField: 背景や枠線がないシンプルなテキスト入力欄です。
- ExposedDropdownMenuBox を使って、編集モードでもカテゴリを変更できるようにしました。
- IconButton(onClick = { Icons.Filled.Done … }): 編集内容を保存するアイコンボタンです。todoItems リストを map 関数で変更し、該当する id の項目だけ copy で新しい text と category に更新します。
- IconButton(onClick = { Icons.Filled.Close … }): 編集をキャンセルするアイコンボタンです。editingItemId = null にするだけで編集モードを終了します(変更は破棄されます)。
- 通常表示モードのUI:
- IconButton(onClick = { Icons.Filled.Edit … }): 編集モードを開始するためのアイコンボタンです。editingItemId を現在の項目の id に設定し、editingText と editingCategory を現在の項目の値で初期化します。
これで、ToDoリストの各項目に編集ボタンと削除ボタンが表示され、編集ボタンを押すとテキストとカテゴリが編集可能になり、保存・キャンセルができるようになります!
※Google AI Studioによる回答を参考にしました
