はじめに
前回はRoomライブラリを使ってデータを永続化しました。今回はDate APIやDatePickerDialog、DialogFragmentを使用し、UIの拡充を行いました。
KotlinでToDoリストアプリを作成 - その1
KotlinでToDoリストアプリを作成 - その2
概要
KotlinでToDoリストアプリを作成します。
プロジェクト全体のソースコードはこちらに置いてあります。
https://github.com/ist-h-i/ToDoApp.git
※JavaやGradleバージョンのアップデートやKapt(Kotlin Annotation Processing Tool)からKSP(Kotlin Symbol Processing)への変更をしております。
今回実装する機能
- リストのソート機能
- 編集機能
- 表示要素の追加
- カレンダー画面の追加
実装に用いる技術
- Date API
- Duration(日付ベースの時間の量「23.4秒」などを表します)
- Period(日付ベースの時間の量「2年3か月と4日」などを表します)
- LocalDate(タイムゾーンのないローカル日付)
- DateTimeFormatter(日付/時間オブジェクトの出力および解析のためのフォーマッタ)
- DatePickerDialog
- DialogFragment
- Kotlinx Coroutines(非同期実行するコードを簡略化できる並行実行のデザイン パターン)
実装
MainActivity.kt (メインクラス)
package com.example.mytodo
import android.annotation.SuppressLint
import android.app.DatePickerDialog
import android.content.DialogInterface
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.view.Window
import android.widget.*
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import androidx.room.Room
import com.example.mytodo.model.Priority
import com.example.mytodo.room.Todo
import com.example.mytodo.room.TodoDAO
import com.example.mytodo.room.TodoDatabase
import com.example.mytodo.ui.DatePick
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.time.Duration
import java.time.LocalDate
import java.time.Period
import java.time.format.DateTimeFormatter
class MainActivity : AppCompatActivity(), DatePickerDialog.OnDateSetListener {
// ToDo追加用の画面を用意(未初期化)
private lateinit var addTodoView: View
// Room Databaseを用意(未初期化)
private lateinit var db: TodoDatabase
private lateinit var dao: TodoDAO
// 表示するリストを用意(今は空)
private var addList = ArrayList<Todo>()
// ソート用のフラグを用意
private var sortedByDeadline = false
private var sortedByPriority = false
// RecyclerViewを宣言
private lateinit var recyclerView: RecyclerView
// RecyclerViewのAdapterを用意(未初期化)
private lateinit var recyclerAdapter: RecyclerAdapter
// 期限日の優先度のための数値
private var deadlineNum: Int = 0
@OptIn(DelicateCoroutinesApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ヘッダータイトルを非表示
supportRequestWindowFeature(Window.FEATURE_NO_TITLE)
// Viewをセット
setContentView(R.layout.activity_main)
// View要素を取得
val btnAdd: Button = findViewById(R.id.btnAdd)
recyclerView = findViewById(R.id.rv)
// コンテンツを変更してもRecyclerViewのレイアウトサイズを変更しない場合はこの設定を使用してパフォーマンスを向上
recyclerView.setHasFixedSize(true)
// レイアウトマネージャーで列数を2列に指定
recyclerView.layoutManager = GridLayoutManager(this, 2, RecyclerView.VERTICAL, false)
val itemDecoration: RecyclerView.ItemDecoration =
DividerItemDecoration(this, DividerItemDecoration.VERTICAL)
recyclerView.addItemDecoration(itemDecoration)
// Roomオブジェクトを初期化
db = Room.databaseBuilder(
this, TodoDatabase::class.java, "todo.db"
).build()
dao = db.todoDAO()
// 初期表示時にToDoレコードを全件取得
GlobalScope.launch {
// RoomからTodoリストを取得
addList = dao.getAll() as ArrayList<Todo>
// recyclerAdapterの初期化
recyclerAdapter = RecyclerAdapter(addList)
recyclerAdapter.setOnCellClickListener(
// ToDoカードビューのクリック処理
object : RecyclerAdapter.OnCellClickListener {
override fun onItemClick(position: Int) {
generateAlertDialog(true, position)
}
})
// RecyclerViewにAdapterをセット
recyclerView.adapter = recyclerAdapter
}
// 追加ボタン押下時にAlertDialogを表示する
btnAdd.setOnClickListener {
generateAlertDialog(false, 0)
}
// 表示しているアイテムがタッチされた時の設定
val itemTouchHelper = ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(
// アイテムをドラッグできる方向を指定
ItemTouchHelper.UP or ItemTouchHelper.DOWN or ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT,
// アイテムをスワイプできる方向を指定
ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
) {
// アイテムドラッグ時の挙動を設定
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder,
): Boolean {
// アイテム位置の入れ替えを行う
val fromPos = viewHolder.adapterPosition
val toPos = target.adapterPosition
recyclerAdapter.notifyItemMoved(fromPos, toPos)
// Roomの情報を更新
val fromData = addList[fromPos]
fromData.displayOrder = toPos
val toData = addList[toPos]
toData.displayOrder = fromPos
GlobalScope.launch {
dao.upsert(fromData)
dao.upsert(toData)
}
return true
}
// アイテムスワイプ時の挙動を設定
@SuppressLint("NotifyDataSetChanged")
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
// アイテムスワイプ時にAlertDialogを表示
android.app.AlertDialog.Builder(this@MainActivity)
// AlertDialogのタイトルを設定
.setTitle(R.string.removeTitle)
// AlertDialogのyesボタンを設定
.setPositiveButton(R.string.yes) { arg0: DialogInterface, _: Int ->
try {
// AlertDialogを非表示
arg0.dismiss()
// Roomから削除
val todo = addList[viewHolder.adapterPosition]
val id =
todo.title.hashCode() / 4 - todo.detail.hashCode() / 4 + todo.deadline.hashCode() / 4 - todo.priority.hashCode() / 4
GlobalScope.launch {
dao.delete(id)
}
// UIスレッドで実行
runOnUiThread {
// スワイプされたアイテムを削除
addList.removeAt(viewHolder.adapterPosition)
// 表示するリストを更新(アイテムが削除されたことを通知)
recyclerAdapter.notifyItemRemoved(viewHolder.adapterPosition)
}
} catch (ignored: Exception) {
}
}.setNegativeButton(R.string.no) { _: DialogInterface, _: Int ->
// 表示するリストを更新(アイテムが変更されたことを通知)
recyclerAdapter.notifyDataSetChanged()
}
// AlertDialogを表示
.show()
}
})
// 表示しているアイテムがタッチされた時の設定をリストに適用
itemTouchHelper.attachToRecyclerView(recyclerView)
}
private fun generateAlertDialog(isUpdate: Boolean, index: Int) {
val nullParent: ViewGroup? = null
addTodoView = layoutInflater.inflate(R.layout.add_todo, nullParent)
// AlertDialog内の表示項目を取得
val txtTitle: EditText = addTodoView.findViewById(R.id.title)
val txtDetail: EditText = addTodoView.findViewById(R.id.detail)
val btnDeadline: Button = addTodoView.findViewById(R.id.deadline)
val priorityButtonGroup: RadioGroup = addTodoView.findViewById(R.id.radioGroup)
if (isUpdate) {
// 編集時
txtTitle.setText(addList[index].title)
txtDetail.setText(addList[index].detail)
btnDeadline.text = addList[index].deadline
when (addList[index].priority) {
Priority.IMPORTANT -> {
priorityButtonGroup.check(R.id.RadioButtonB1)
}
Priority.NORMAL -> {
priorityButtonGroup.check(R.id.RadioButtonB2)
}
Priority.UNIMPORTANT -> {
priorityButtonGroup.check(R.id.RadioButtonB3)
}
}
} else {
// 新規登録時
priorityButtonGroup.check(R.id.RadioButtonB3)
}
// AlertDialogを生成
android.app.AlertDialog.Builder(this)
// AlertDialogのタイトルを設定
.setTitle(R.string.addTitle)
// AlertDialogの表示項目を設定
.setView(addTodoView)
// AlertDialogのyesボタンを設定し、押下時の挙動を記述
.setPositiveButton(R.string.yes) { _: DialogInterface, _: Int ->
if (isUpdate) {
// 編集時
val id = addList[index].id
val displayOrder = addList[index].displayOrder
// 入力内容を取得
val title = txtTitle.text.toString()
val detail = txtDetail.text.toString()
val deadline = btnDeadline.text.toString()
val deadlineNum = if (deadline == R.string.deadline.toString()) Int.MAX_VALUE else deadlineNum
val priority = when (priorityButtonGroup.checkedRadioButtonId) {
R.id.RadioButtonB1 -> Priority.IMPORTANT
R.id.RadioButtonB2 -> Priority.NORMAL
R.id.RadioButtonB3 -> Priority.UNIMPORTANT
else -> Priority.IMPORTANT
}
updateTodoData(
Todo(
id, displayOrder, title, detail, deadline, deadlineNum, priority
), index
)
} else {
// 新規登録時
// 入力内容を取得
val title = txtTitle.text.toString()
val detail = txtDetail.text.toString()
val deadline = btnDeadline.text.toString()
val deadlineNum = if (deadline == R.string.deadline.toString()) Int.MAX_VALUE else deadlineNum
val priority = when (priorityButtonGroup.checkedRadioButtonId) {
R.id.RadioButtonB1 -> Priority.IMPORTANT
R.id.RadioButtonB2 -> Priority.NORMAL
R.id.RadioButtonB3 -> Priority.UNIMPORTANT
else -> Priority.IMPORTANT
}
val id =
title.hashCode() / 4 - detail.hashCode() / 4 + deadline.hashCode() / 4 - priority.hashCode() / 4
// ToDoを生成
insertTodoData(
Todo(
id, addList.size, title, detail, deadline, deadlineNum, priority
)
)
}
}
// AlertDialogのnoボタンを設定
.setNegativeButton(R.string.no, null)
// AlertDialogを表示
.show()
}
@OptIn(DelicateCoroutinesApi::class)
private fun insertTodoData(data: Todo) {
// Todoアイテムの重複をチェック
if (addList.stream().anyMatch { e ->
e.title == data.title && e.detail == data.detail && e.deadline == data.deadline && e.priority == data.priority
}) {
Toast.makeText(
this, R.string.duplicateTitle, Toast.LENGTH_SHORT
).show()
return
}
// 表示するリストの最後尾に追加
addList.add(data)
// 表示するリストを更新(アイテムが挿入されたことを通知)
recyclerAdapter.notifyItemInserted(addList.size - 1)
GlobalScope.launch {
// Roomに追加
dao.upsert(data)
}
}
@OptIn(DelicateCoroutinesApi::class)
private fun updateTodoData(data: Todo, index: Int) {
// Todoアイテムの重複をチェック
if (addList.indexOf(data) != index && addList.stream().anyMatch { e ->
e.title == data.title && e.detail == data.detail && e.deadline == data.deadline && e.priority == data.priority
}) {
Toast.makeText(
this, R.string.duplicateTitle, Toast.LENGTH_SHORT
).show()
return
}
// 表示するリストの更新
addList[index] = data
// 表示するリストを更新(アイテムが更新されたことを通知)
recyclerAdapter.notifyItemChanged(index)
GlobalScope.launch {
// Roomに追加
dao.upsert(data)
}
}
override fun onDateSet(view: DatePicker, year: Int, monthOfYear: Int, dayOfMonth: Int) {
// カレンダー入力時
val dateFormat = DateTimeFormatter.ofPattern("uu/MM/dd(E)")
val deadline: LocalDate = LocalDate.of(year, monthOfYear + 1, dayOfMonth)
val period: Period = Period.between(LocalDate.now(), deadline)
val duration: Duration = Duration.between(
LocalDate.now().atTime(0, 0, 0), deadline.atTime(0, 0, 0)
)
val remainYears = if (period.years != 0) period.years + R.string.yearUnit else ""
val remainMonths = if (period.months != 0) period.months + R.string.monthUnit else ""
val txtDeadline =
if (period.isNegative) R.string.expired else dateFormat.format(deadline) + "\n" + R.string.remain + remainYears + remainMonths + period.days + R.string.dayUnit
addTodoView.findViewById<Button>(R.id.deadline).text = txtDeadline.toString()
deadlineNum = duration.toDays().toInt()
}
fun showDatePickerDialog(@Suppress("UNUSED_PARAMETER") v: View) {
// カレンダー表示
val newFragment = DatePick()
newFragment.show(supportFragmentManager, "datePicker")
}
@SuppressLint("NotifyDataSetChanged")
fun orderByPriority(@Suppress("UNUSED_PARAMETER") v: View) {
// 優先度でソート
sortedByPriority = !sortedByPriority
if (addList.isEmpty()) {
Toast.makeText(
this, R.string.emptyTodoList, Toast.LENGTH_SHORT
).show()
}
addList.sortWith(compareBy<Todo> { it.priority }.thenBy { it.displayOrder })
if (sortedByPriority) {
addList.reverse()
}
// 表示するリストを更新(アイテムが変更されたことを通知)
recyclerAdapter.notifyDataSetChanged()
}
@SuppressLint("NotifyDataSetChanged")
fun orderByDeadline(@Suppress("UNUSED_PARAMETER") v: View) {
// 期限日でソート
sortedByDeadline = !sortedByDeadline
if (addList.isEmpty()) {
Toast.makeText(
this, R.string.emptyTodoList, Toast.LENGTH_SHORT
).show()
}
addList.sortWith(compareBy<Todo> { it.deadlineNum }.thenBy { it.displayOrder })
if (sortedByDeadline) {
addList.reverse()
}
// 表示するリストを更新(アイテムが変更されたことを通知)
recyclerAdapter.notifyDataSetChanged()
}
}
RecyclerAdapter.kt (アイテムを生成し、リストに適用するクラス)
package com.example.mytodo
import android.graphics.Color
import android.graphics.Typeface
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.cardview.widget.CardView
import androidx.recyclerview.widget.RecyclerView
import com.example.mytodo.model.Priority
import com.example.mytodo.room.Todo
class RecyclerAdapter(private val todoList: ArrayList<Todo>) :
RecyclerView.Adapter<RecyclerAdapter.ViewHolderItem>() {
// リスナを格納する変数を定義(未初期化)
private lateinit var listener: OnCellClickListener
// インターフェースを作成
interface OnCellClickListener {
fun onItemClick(position: Int)
}
// リスナーをセット
fun setOnCellClickListener(clickListener: OnCellClickListener) {
listener = clickListener
}
// リストに表示するアイテムの表示内容
inner class ViewHolderItem(v: View) : RecyclerView.ViewHolder(v) {
val titleHolder: TextView = v.findViewById(R.id.title)
val detailHolder: TextView = v.findViewById(R.id.detail)
val deadlineHolder: TextView = v.findViewById(R.id.deadline)
var cardView: CardView = v.findViewById(R.id.cardView)
}
// リストに表示するアイテムを生成
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolderItem {
val view = LayoutInflater.from(parent.context).inflate(R.layout.one_layout, parent, false)
return ViewHolderItem(view)
}
// position番目のデータを表示
override fun onBindViewHolder(holder: ViewHolderItem, position: Int) {
val currentItem = todoList[position]
holder.titleHolder.text = currentItem.title
holder.detailHolder.text = currentItem.detail
holder.deadlineHolder.text = currentItem.deadline
if (currentItem.deadline == "期限切れ") {
holder.deadlineHolder.setTextColor(Color.RED)
holder.deadlineHolder.typeface = Typeface.DEFAULT_BOLD
} else {
holder.deadlineHolder.setTextColor(Color.GRAY)
holder.deadlineHolder.typeface = Typeface.DEFAULT
}
val frameColor = when (currentItem.priority) {
Priority.IMPORTANT -> Color.parseColor("#FF4444")
Priority.NORMAL -> Color.parseColor("#FFBB33")
else -> Color.parseColor("#33B5E5")
}
holder.cardView.setCardBackgroundColor(frameColor)
holder.cardView.setOnClickListener {
listener.onItemClick(position)
}
}
// リストサイズを取得する用のメソッド
override fun getItemCount(): Int {
return todoList.size
}
}
DatePick.kt (カレンダー生成)
package com.example.mytodo.ui
import android.app.DatePickerDialog
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import com.example.mytodo.MainActivity
import java.time.LocalDateTime
class DatePick : DialogFragment(), DatePickerDialog.OnDateSetListener {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val today: LocalDateTime = LocalDateTime.now()
val year = today.year
val month = today.monthValue - 1
val day = today.dayOfMonth
return DatePickerDialog(
this.context as Context, activity as MainActivity?, year, month, day
)
}
override fun onDateSet(
view: android.widget.DatePicker, year: Int, monthOfYear: Int, dayOfMonth: Int
) {
}
}
Priority.kt (ToDoの優先度をEnumで定義)
package com.example.mytodo.model
enum class Priority(val value: Int) {
IMPORTANT(1),
NORMAL(2),
UNIMPORTANT(3);
companion object {
fun fromInt(value: Int) = Priority.values().first { it.value == value }
}
}
one_layout.xml (リストに表示する内容)
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/cardView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardBackgroundColor="@android:color/holo_blue_light"
app:cardCornerRadius="6dp"
app:cardElevation="6dp"
app:cardUseCompatPadding="true"
app:contentPadding="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:orientation="vertical"
android:padding="5dp">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:lines="2"
android:textColor="@android:color/black"
android:textSize="15sp"
android:textStyle="bold" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?android:attr/listDivider" />
<TextView
android:id="@+id/detail"
android:layout_width="match_parent"
android:layout_height="80dp"
android:lines="5"
android:textColor="@android:color/darker_gray"
android:textSize="13sp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?android:attr/listDivider" />
<TextView
android:id="@+id/deadline"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:lines="2"
android:text="@string/deadline"
android:textColor="@android:color/darker_gray"
android:textSize="11sp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
add_todo.xml (ToDo追加ボタン押下時に表示するビューの内容)
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:autofillHints="auto"
android:hint="@string/title"
android:inputType="textMultiLine"
android:textSize="20sp"
app:layout_constraintBottom_toTopOf="@id/detail"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="VisualLintTextFieldSize" />
<EditText
android:id="@+id/detail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:autofillHints="auto"
android:breakStrategy="simple"
android:hint="@string/detail"
android:inputType="textMultiLine"
android:textSize="20sp"
app:layout_constraintBottom_toTopOf="@+id/attribute"
app:layout_constraintTop_toBottomOf="@id/title" />
<LinearLayout
android:id="@+id/attribute"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
app:layout_constraintBottom_toTopOf="@id/deadline"
app:layout_constraintTop_toBottomOf="@id/detail">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="10dp"
android:text="@string/priority"
android:textSize="15sp" />
<RadioGroup
android:id="@+id/radioGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<RadioButton
android:id="@+id/RadioButtonB1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@android:color/holo_red_light"
android:gravity="center"
android:text="@string/high"
android:textSize="15sp" />
<RadioButton
android:id="@+id/RadioButtonB2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@android:color/holo_orange_light"
android:gravity="center"
android:text="@string/middle"
android:textSize="15sp" />
<RadioButton
android:id="@+id/RadioButtonB3"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@android:color/holo_blue_light"
android:gravity="center"
android:text="@string/low"
android:textSize="15sp" />
</RadioGroup>
</LinearLayout>
<Button
android:id="@+id/deadline"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="5pt"
android:hint="@string/deadline"
android:onClick="showDatePickerDialog"
android:textColor="@color/white"
android:textColorHint="@color/white"
android:textSize="15sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/attribute" />
</androidx.constraintlayout.widget.ConstraintLayout>
main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_margin="0dp"
app:layout_constraintBottom_toTopOf="@id/buttonArea"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="@+id/buttonArea"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="0dp"
android:gravity="end"
app:layout_constraintBottom_toBottomOf="parent">
<Button
android:id="@+id/btnOrderPriority"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:layout_marginBottom="10dp"
android:backgroundTint="@color/purple_200"
android:onClick="orderByPriority"
android:text="@string/priorityOrder"
android:textSize="15sp" />
<Button
android:id="@+id/btnOrderDeadline"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:layout_marginBottom="10dp"
android:backgroundTint="@color/purple_200"
android:onClick="orderByDeadline"
android:text="@string/deadlineOrder"
android:textSize="15sp" />
<Button
android:id="@+id/btnAdd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:layout_marginBottom="10dp"
android:text="@string/addButton"
android:textSize="15sp"
tools:ignore="ButtonStyle" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
プロジェクトビルド&実行
ヘッダーメニューよりBuild > Rebuild Projectを行いエラーが出ないことを確認して実行
まとめ
今回はUIの拡充を行いました。Date APIやDialogFragmentは使用機会が多いので使い慣れておきたいですね。
次回はこのアプリにAWS DynomoDBとの接続やFragmentを使った画面遷移などを追加して、タスク管理アプリを作成する予定です。