오늘은 Recyclerview와 함께 CRUD기능을 가진 Todo List를 만들어보려고 합니다. (Figma는 직접 만들었습니다!)
https://www.figma.com/file/9K5uSHkUOUjiBM0EqwpkeP/Untitled?type=design&node-id=0%3A1&mode=design&t=ReOqselNU6DLOx8A-1
처음 시작은 아래 링크를 통해 clone을 받아서 시작해주시면 됩니다. 해당 Repo에는 기본적인 ViewBinding, Color 설정이 완료되어 있습니다.
https://github.com/JGeun/TodoList/tree/started
※ 작업하다가 추가된 내용들입니다. 넣어주시며 감사하겠습니다 :)
// strings.xml
<string name="progress_task_rate">%d/%d 완료</string>
<string name="progress_task_rate">%d/%d 완료</string>
<string name="task_done_msg">오늘도 고생하셨어요!</string>
ic_box_check_white.xml은 제가 하나 더 넣었지만 기존의 ic_check_white.xml을 사용하셔도 괜찮습니다.
<drawable>
각 컴포넌트별 형태를 맞추기 위해 Custom했습니다.
// bg_add_btn
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners
android:radius="10dp" />
<solid
android:color="@color/add_btn_bg_color" />
</shape>
// bg_dialog_btn
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<stroke
android:width="2dp"
android:color="@color/dialog_btn_stroke_color" />
<corners android:radius="10dp" />
<solid android:color="@color/white" />
</shape>
// bg_progress_area
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<stroke
android:width="1dp"
android:color="@color/progress_area_stroke_color"/>
<corners
android:radius="20dp" />
<solid
android:color="@color/white" />
</shape>
// bg_progress_bar
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<corners android:radius="8dp"/>
<solid android:color="@color/progress_bar_default"/>
</shape>
</item>
<item android:id="@android:id/progress"
android:top="1dp"
android:bottom="1dp"
android:left="1dp"
android:right="1dp">
<scale android:scaleWidth="100%">
<shape>
<corners android:radius="8dp"/>
<solid android:color="@color/progress_bar_completed"/>
</shape>
</scale>
</item>
</layer-list>
// bg_task_dialog
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners
android:radius="10dp" />
<solid
android:color="@color/white" />
</shape>
// bg_task_blue, bg_task_emerald, bg_task_green, bg_task_purple, bg_task_sky
// 각 color 코드만 변경해주시면 됩니다. (list_color1~5)
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="5dp" />
<solid android:color="@color/list_color4" />
</shape>
기능 코드 구현
지금부터 이 부분을 MVVM 형태로 구현하겠습니다.
저희는 하루에 해야할 일들을 Task Data Class로 정하겠습니다.
data class Task(
// Task 고유 ID
val id: Int,
val title: String,
val content: String,
// 선택한 배경색 번호
val bgColor: TaskBgColor,
val isFinish: Boolean = false
)
backgroundColor는 enum class로 설정하고 property로 drawableRes를 함께 저장했습니다.
enum class TaskBgColor(@DrawableRes val background: Int) {
PURPLE(R.drawable.bg_task_purple),
GREEN(R.drawable.bg_task_green),
BLUE(R.drawable.bg_task_blue),
SKY(R.drawable.bg_task_sky),
EMERALD(R.drawable.bg_task_emerald);
}
ViewModel은 View와의 의존성이 분리되고 핵심 기능들과 데이터를 관리하게 됩니다.
class TaskViewModel : ViewModel() {
private val taskList = mutableListOf<Task>()
private var generatedId = 0
private var deleteCnt = 0
private var finishCnt = 0
private val _taskLiveData = MutableLiveData<List<Task>>()
val taskLiveData: LiveData<List<Task>>
get() = _taskLiveData
init {
_taskLiveData.value = taskList.toList()
}
fun getTotalTaskCnt() = generatedId - deleteCnt
fun getTaskFinishCnt() = finishCnt
fun addTask(title: String, content: String, taskBgColor: TaskBgColor) {
taskList.add(createTask(title, content, taskBgColor))
_taskLiveData.value = taskList.toList()
}
fun deleteTask(task: Task) {
deleteCnt = deleteCnt.plus(1)
taskList.remove(task)
_taskLiveData.value = taskList.toList()
}
fun finishTask(task: Task) {
finishCnt = finishCnt.plus(1)
taskList.remove(task)
_taskLiveData.value = taskList.toList()
}
private fun createTask(title: String, content: String, taskBgColor: TaskBgColor): Task {
generatedId = generatedId.plus(1)
return Task(id = generatedId, title = title, content = content, bgColor = taskBgColor)
}
}
<MainActivity>
Activity 부터 만들어보겠습니다.
그 전에 build.gradle(Module :app)에서 다음을 추가해주셔야 합니다.
implementation 'androidx.activity:activity-ktx:1.7.2'
implementation 'androidx.fragment:fragment-ktx:1.6.0'
만약 이걸 추가안하신다면 viewModels를 사용하실 수 없습니다.
layout
<?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"
android:background="@color/white"
android:paddingHorizontal="19dp"
android:paddingVertical="41dp"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/title"
android:textColor="@color/black"
android:textSize="24sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/btn_add"
android:layout_width="36dp"
android:layout_height="36dp"
android:background="@drawable/bg_add_btn"
android:padding="18dp"
android:src="@drawable/ic_add_white"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/tv_title" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/progress_area"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="34dp"
android:background="@drawable/bg_progress_area"
android:paddingHorizontal="16dp"
android:paddingVertical="20dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_title">
<TextView
android:id="@+id/tv_progress_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/progress_title_start"
android:textColor="@color/black"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_progress_percent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="0%"
android:textColor="@color/black"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_progress_rate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:textColor="@color/black"
android:textSize="14sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_progress_title"
tools:text="0/0 완료" />
<ProgressBar
android:id="@+id/pb_progress"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="0dp"
android:layout_height="15dp"
android:layout_marginTop="10dp"
android:max="100"
android:progressDrawable="@drawable/bg_progress_bar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_progress_rate"
tools:progress="30" />
</androidx.constraintlayout.widget.ConstraintLayout>
<TextView
android:id="@+id/task_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="37dp"
android:textColor="@color/black"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/progress_area"
tools:text="현재 0개의 할 일이 남아 있어요" />
<TextView
android:id="@+id/task_msg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/task_empty_msg"
android:textColor="@color/msg_color"
android:textSize="20sp"
android:textStyle="bold"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/task_title"
tools:visibility="visible" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_task"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="21dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/task_title" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity
class MainActivity : AppCompatActivity() {
private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
private val viewModel by viewModels<TaskViewModel>()
private var taskAdapter: TaskAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
initUI()
observeData()
}
private fun initUI() {
taskAdapter = TaskAdapter(
taskFinishEvent = { task -> viewModel.finishTask(task) },
taskDeleteEvent = { task -> viewModel.deleteTask(task) }
)
binding.btnAdd.setOnClickListener {
TaskDialog{ title, content, bgColor ->
viewModel.addTask(title, content, bgColor)
}.show(supportFragmentManager, "TaskDialog")
}
with(binding.rvTask) {
setHasFixedSize(true)
adapter = taskAdapter
layoutManager = LinearLayoutManager(this@MainActivity)
}
}
private fun observeData() {
viewModel.taskLiveData.observe(this) { taskList ->
binding.taskTitle.text = resources.getString(R.string.task_title, taskList.size)
// 완료한 개수 / 전체 비율 -> ex) 0/3 완료
val taskFinishCnt = viewModel.getTaskFinishCnt()
val taskTotalCnt = viewModel.getTotalTaskCnt()
binding.tvProgressRate.text = resources.getString(R.string.progress_task_rate, taskFinishCnt, taskTotalCnt)
// progressbar 설정
with(binding.pbProgress) {
progress = taskFinishCnt
max = taskTotalCnt
}
val percent = if (taskTotalCnt == 0) 0 else taskFinishCnt*100/taskTotalCnt
binding.tvProgressPercent.text = resources.getString(R.string.progress_title, percent)
// 설정된 Progress Rate에 따라 Text 변경 정책
binding.tvProgressTitle.text = getProgressTitle(percent)
// TaskMsg는 Task가 존재할 때는 보이지 않습니다
binding.taskMsg.text = getTaskMsg(taskTotalCnt)
binding.taskMsg.visibility = if (taskList.isEmpty()) View.VISIBLE else View.INVISIBLE
taskAdapter?.submitList(taskList)
}
}
private fun getProgressTitle(percent: Int): String {
return if (percent == 100) {
resources.getString(R.string.progress_title_finish)
} else if (percent >= 30) {
resources.getString(R.string.progress_title_ing)
} else {
resources.getString(R.string.progress_title_start)
}
}
private fun getTaskMsg(totalCnt: Int): String {
return if (totalCnt == 0)
resources.getString(R.string.task_empty_msg)
else
resources.getString(R.string.task_done_msg)
}
}
<TaskAdapter>
Adapter에서는 RecyclerView에 연결해줄 데이터들을 처리합니다. 저는 ListAdapter를 사용해서 구현했습니다.
일반적인 Adapter와 ListAdapter의 차이가 궁금하시다면 다음 링크를 참고해주세요!
layout
<?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:id="@+id/item_task_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="14dp"
tools:background="@drawable/bg_task_purple">
<TextView
android:id="@+id/tv_item_task_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:textColor="@color/white"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/tv_item_task_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="spread_inside"
tools:text="Title" />
<TextView
android:id="@+id/tv_item_task_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:textColor="@color/white"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@id/tv_item_task_title"
app:layout_constraintTop_toBottomOf="@+id/tv_item_task_title"
tools:text="Title" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/ic_item_task_delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="21dp"
android:background="@null"
android:src="@drawable/ic_delete_white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/ic_item_task_finish"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="21dp"
android:background="@null"
android:src="@drawable/ic_check_white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/ic_item_task_delete"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
TaskAdapter
class TaskAdapter(
private val taskFinishEvent: (Task) -> Unit,
private val taskDeleteEvent: (Task) -> Unit
) : ListAdapter<Task, TaskAdapter.TaskViewHolder>(TaskDiffUtil) {
inner class TaskViewHolder(
private val binding: ItemTaskBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind(task: Task) {
val finishClickListener = View.OnClickListener { taskFinishEvent(task) }
val deleteClickListener = View.OnClickListener { taskDeleteEvent(task) }
with(binding) {
itemTaskLayout.background = AppCompatResources.getDrawable(itemView.context, task.bgColor.background)
tvItemTaskTitle.text = task.title
tvItemTaskContent.text = task.content
icItemTaskFinish.setOnClickListener(finishClickListener)
icItemTaskDelete.setOnClickListener(deleteClickListener)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TaskViewHolder {
val binding = ItemTaskBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return TaskViewHolder(binding)
}
override fun onBindViewHolder(holder: TaskViewHolder, position: Int) {
holder.bind(currentList[position])
}
companion object {
object TaskDiffUtil : ItemCallback<Task>() {
override fun areItemsTheSame(oldItem: Task, newItem: Task): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: Task, newItem: Task): Boolean {
return oldItem.id == newItem.id
}
}
}
}
<TaskDialog>
이제 + 버튼을 눌렀을 때 나타나는 Dialog입니다.
layout
<?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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_task_dialog"
android:paddingHorizontal="25dp"
android:paddingVertical="28dp">
<TextView
android:id="@+id/dialog_task_tv_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="제목"
android:textColor="@color/black"
android:textSize="24sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/dialog_task_et_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="7dp"
android:maxLines="1"
android:paddingBottom="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dialog_task_tv_title" />
<TextView
android:id="@+id/dialog_task_tv_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:text="내용"
android:textColor="@color/black"
android:textSize="24sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dialog_task_et_title" />
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/dialog_task_et_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="7dp"
android:maxLines="1"
android:paddingBottom="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dialog_task_tv_content" />
<FrameLayout
android:id="@+id/dialog_purple_box"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_marginTop="21dp"
android:background="@drawable/bg_task_purple"
android:clickable="true"
android:focusable="true"
app:layout_constraintEnd_toStartOf="@+id/dialog_green_box"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dialog_task_et_content">
<View
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/dialog_purple_box_check"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="5dp"
android:src="@drawable/ic_box_check_white" />
</FrameLayout>
<FrameLayout
android:id="@+id/dialog_green_box"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_marginTop="21dp"
android:background="@drawable/bg_task_green"
android:clickable="true"
android:focusable="true"
app:layout_constraintEnd_toStartOf="@+id/dialog_blue_box"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/dialog_purple_box"
app:layout_constraintTop_toBottomOf="@id/dialog_task_et_content">
<View
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/dialog_green_box_check"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="5dp"
android:src="@drawable/ic_box_check_white"
android:visibility="gone" />
</FrameLayout>
<FrameLayout
android:id="@+id/dialog_blue_box"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_marginTop="21dp"
android:background="@drawable/bg_task_blue"
android:clickable="true"
android:focusable="true"
app:layout_constraintEnd_toStartOf="@+id/dialog_sky_box"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/dialog_green_box"
app:layout_constraintTop_toBottomOf="@id/dialog_task_et_content">
<View
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/dialog_blue_box_check"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="5dp"
android:src="@drawable/ic_box_check_white"
android:visibility="gone" />
</FrameLayout>
<FrameLayout
android:id="@+id/dialog_sky_box"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_marginTop="21dp"
android:background="@drawable/bg_task_sky"
android:clickable="true"
android:focusable="true"
app:layout_constraintEnd_toStartOf="@+id/dialog_emerald_box"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/dialog_blue_box"
app:layout_constraintTop_toBottomOf="@id/dialog_task_et_content">
<View
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/dialog_sky_box_check"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="5dp"
android:src="@drawable/ic_box_check_white"
android:visibility="gone" />
</FrameLayout>
<FrameLayout
android:id="@+id/dialog_emerald_box"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_marginTop="21dp"
android:background="@drawable/bg_task_emerald"
android:clickable="true"
android:focusable="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/dialog_sky_box"
app:layout_constraintTop_toBottomOf="@id/dialog_task_et_content">
<View
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/dialog_emerald_box_check"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="5dp"
android:src="@drawable/ic_box_check_white"
android:visibility="gone" />
</FrameLayout>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/dialog_task_btn_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="38dp"
android:background="@drawable/bg_dialog_btn"
android:text="취소"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/dialog_purple_box" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/dialog_task_btn_store"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:background="@drawable/bg_dialog_btn"
android:text="저장"
app:layout_constraintEnd_toStartOf="@id/dialog_task_btn_cancel"
app:layout_constraintTop_toTopOf="@id/dialog_task_btn_cancel" />
</androidx.constraintlayout.widget.ConstraintLayout>
TaskDialog
class TaskDialog(
private val addTask: (String, String, TaskBgColor) -> Unit
) : DialogFragment() {
private lateinit var binding: DialogTaskBinding
private lateinit var selectedBoxCheckView: ImageView
private var selectedColor: TaskBgColor = TaskBgColor.PURPLE
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = DialogTaskBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.dialogTaskBtnStore.setOnClickListener {
val title = binding.dialogTaskEtTitle.text.toString()
val content = binding.dialogTaskEtContent.text.toString()
addTask(title, content, selectedColor)
dismissAllowingStateLoss()
}
binding.dialogTaskBtnCancel.setOnClickListener {
dismissAllowingStateLoss()
}
initBgColorEvent()
}
override fun onResume() {
super.onResume()
// xml에서 테두리 변경 및 크기 조절이 안되기에 적용했습니다.
dialogLayoutSetting()
}
private fun initBgColorEvent() {
selectedBoxCheckView = binding.dialogPurpleBoxCheck
binding.dialogPurpleBox.setOnClickListener { setBoxCheckVisibility(binding.dialogPurpleBoxCheck, TaskBgColor.PURPLE) }
binding.dialogGreenBox.setOnClickListener { setBoxCheckVisibility(binding.dialogGreenBoxCheck, TaskBgColor.GREEN) }
binding.dialogBlueBox.setOnClickListener { setBoxCheckVisibility(binding.dialogBlueBoxCheck, TaskBgColor.BLUE) }
binding.dialogSkyBox.setOnClickListener { setBoxCheckVisibility(binding.dialogSkyBoxCheck, TaskBgColor.SKY) }
binding.dialogEmeraldBox.setOnClickListener { setBoxCheckVisibility(binding.dialogEmeraldBoxCheck, TaskBgColor.EMERALD) }
}
private fun setBoxCheckVisibility(selectedBoxCheck: ImageView, selectedColor: TaskBgColor) {
// 이전 선택된 박스 체크 이미지의 visibility Gone
selectedBoxCheckView.visibility = View.GONE
// 이번에 선택된 check 이미지 설정
selectedBoxCheckView = selectedBoxCheck
selectedBoxCheckView.visibility = View.VISIBLE
this.selectedColor = selectedColor
}
private fun dialogLayoutSetting() {
val params: ViewGroup.LayoutParams? = dialog?.window?.attributes
params?.width = (getScreenWidth() * 0.9).toInt()
dialog?.window?.run {
attributes = params as WindowManager.LayoutParams
setBackgroundDrawable(AppCompatResources.getDrawable(requireContext(), R.drawable.bg_task_dialog))
}
}
private fun getScreenWidth(): Int {
val windowManager = requireContext().getSystemService(Context.WINDOW_SERVICE) as WindowManager
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val windowMetrics = windowManager.currentWindowMetrics
val insets = windowMetrics.windowInsets
.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars())
windowMetrics.bounds.width() - insets.left - insets.right
} else {
val displayMetrics = DisplayMetrics()
windowManager.defaultDisplay.getMetrics(displayMetrics)
displayMetrics.widthPixels
}
}
}
여기까지 진행하셨다면 아마 Task들이 겹쳐서 나올겁니다. 이 때는 ItemDecoration을 설정해주시면 됩니다.
class TaskItemDecoration(
private val spacing: Int
) : RecyclerView.ItemDecoration(){
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
super.getItemOffsets(outRect, view, parent, state)
val position = parent.getChildAdapterPosition(view)
if (position == 0) return
outRect.top = spacing
}
}
// MainActivity
with(binding.rvTask) {
setHasFixedSize(true)
adapter = taskAdapter
layoutManager = LinearLayoutManager(this@MainActivity)
addItemDecoration(TaskItemDecoration(40))
}
<코드 링크>
https://github.com/JGeun/TodoList
궁금하거나 에러가 있다면 댓글로 남겨주세요. 빠르게 해결해드리겠습니다!
'Android' 카테고리의 다른 글
2022 Android Roadmap (안드로이드 로드맵) (0) | 2022.04.23 |
---|---|
[Android] Recyclerview 에 대해 알아보자! (0) | 2022.01.17 |
한성프렌즈! 나의 성격에 맞는 한성프렌즈 캐릭터를 알아보자! (0) | 2021.12.24 |
소프트 스퀘어드 11기 수료 및 후기 (0) | 2021.01.26 |
소프트스퀘어드 11기 수료 프로젝트: Flo 앱 클론코딩으로 제작 (0) | 2021.01.25 |
댓글