Android Studio

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

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

タスクを編集できるようにするには、以下のことを行います。

  1. 編集モードの管理
    どのタスクが現在編集モードにあるかを管理するための状態を導入します。
  2. 編集用の TextField を表示する
    タスクが編集モードの場合、そのタスクの表示テキストを TextField に置き換えます。
  3. 編集を保存するボタン(またはアイコン)を追加する
    編集モードの TextField の横に、変更を確定するためのボタン(またはアイコン)を追加します。
  4. 編集をキャンセルするボタン(またはアイコン)を追加する
    編集モードを終了し、変更を破棄するためのボタン(またはアイコン)を追加します。

具体的な手順

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による回答を参考にしました

目次に戻る