본문 바로가기
Android

[Android] Todo List만들기 (CRUD 공부)

by 너츠너츠 2022. 1. 17.

오늘은 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


궁금하거나 에러가 있다면 댓글로 남겨주세요. 빠르게 해결해드리겠습니다!

반응형

댓글