그냥 가끔의 기록장

[Kotlin] Todo 토이 프로젝트 [2단계] (ViewPager2 + RecyclerView + ViewModel) 본문

Android

[Kotlin] Todo 토이 프로젝트 [2단계] (ViewPager2 + RecyclerView + ViewModel)

the phoenix 2022. 4. 23. 17:18

https://soeun-87.tistory.com/32

 

[Kotlin] Todo 토이 프로젝트 [1단계] (ViewPager2 + RecyclerView + ViewModel)

아주 오랜만에 블로그에 글을 쓰는데, 최근에 진행한 Todo 토이 프로젝트를 순서대로 작성해볼까 한다. Github에 순서대로 commit도 했으니 참고하면 좋을 것 같다. 최종 결과물은 아래 움짤과 같다.

soeun-87.tistory.com

앞 글에 이어 2단계로 토이 프로젝트를 마저 구현해 보자.

 

1. 단계별 코드

(1) RecyclerView Adapter 생성 - DoneFragment, PendingFragment 용

  DoneFragment와 Pendingfragment는 각각 RecyclerView를 갖고서 Todo Item들을 보여줘야 하므로, Adapter가 필요하다.

두 가지 Adapter는 지금 당장은 구조가 완전히 동일하므로 (다음 글의 viewModel 도입 시 조금 달라짐, 지금은 같음) TodoPendingAdapter.kt만 설명하고 넘어가겠다.

 

  TodoPendingAdapter는 RecyclerView.Adapter를 상속 받고, TodoPendingAdapter안에 inner class로 TodoViewHolder를 작성해준다. 여기서 ViewHolder는 각 data item을 view를 사용해 화면에 보여주는 역할을 한다.

 

  순서대로 보면

 

1. TodoViewHolder 작성 및, 내부에 bind() 함수를 작성해 각 Todos list에 있는 data item과 View를 bind해서 title은 tvTitle view에 표시되도록 한다.

 

2. RecyclerView.Adapter 상속 시 onCreateViewHolder, onBindViewHolder, getItemCount 3가지 함수를 반드시 override 해야 한다. 각 함수가 무슨 역할을 하는지는 주석을 참고하면 된다. 

 

3. 마지막으로 getItemId()라는 함수를 작성해 RecyclerView의 item position에 맞는 id를 반환해준다. 

  

1) TodoPendingAdapter.kt

package com.example.kotlintodo.adapter

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.kotlintodo.databinding.ItemTodoBinding
import com.example.kotlintodo.model.Todo

class TodoPendingAdapter(var Todos: List<Todo>): RecyclerView.Adapter<TodoPendingAdapter.ToDoViewHolder>() {

    private lateinit var itemBinding: ItemTodoBinding

    inner class ToDoViewHolder(private val itemBinding: ItemTodoBinding): RecyclerView.ViewHolder(itemBinding.root){
        fun bind(data: Todo) {
            itemBinding.tvTitle.text = data.title
            itemBinding.tvContent.text = data.content
            itemBinding.cbIsDone.isChecked = data.isDone
        }
    }

    // RecyclerView.Adapter 상속 시 무조건 override 해야하는 fun - viewHolder에 layout inflate 하는 함수 (ViewBinding 사용)
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ToDoViewHolder {
        itemBinding = ItemTodoBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return ToDoViewHolder(itemBinding)
    }

    // RecyclerView.Adapter 상속 시 무조건 override 해야하는 fun - viewHolder에 각 view를 bind하는 함수
    // ToDoViewHolder내에 bind함수 정의했으므로, 각 Todos[position]인 item data랑 view를 bind하면 됨
    override fun onBindViewHolder(holder: ToDoViewHolder, position: Int) {
        holder.bind(Todos[position])
    }

    // RecyclerView.Adapter 상속 시 무조건 override 해야하는 fun - 보통 Todos.size를 return, RecyclerView내의 item 개수 return하는 함수
    override fun getItemCount(): Int {
        return Todos.size
    }

    // Todos list의 각 item id return
    override fun getItemId(position: Int): Long {
        return position.toLong()
    }

}

 

2) TodoDoneAdapter.kt

package com.example.kotlintodo.adapter

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.kotlintodo.databinding.ItemTodoBinding
import com.example.kotlintodo.model.Todo

class TodoDoneAdapter(var Todos: List<Todo>): RecyclerView.Adapter<TodoDoneAdapter.ToDoViewHolder>() {

    private lateinit var itemBinding: ItemTodoBinding

    inner class ToDoViewHolder(private val itemBinding: ItemTodoBinding): RecyclerView.ViewHolder(itemBinding.root){
        fun bind(data:Todo) {
            itemBinding.tvTitle.text = data.title
            itemBinding.tvContent.text = data.content
            itemBinding.cbIsDone.isChecked = data.isDone
        }
    }

    // RecyclerView.Adapter 상속 시 무조건 override 해야하는 fun - viewHolder에 layout inflate 하는 함수 (ViewBinding 사용)
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ToDoViewHolder {
        itemBinding = ItemTodoBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return ToDoViewHolder(itemBinding)
    }

    // RecyclerView.Adapter 상속 시 무조건 override 해야하는 fun - viewHolder에 각 view를 bind하는 함수
    // ToDoViewHolder내에 bind함수 정의했으므로, 각 Todos[position]인 item data랑 view를 bind하면 됨
    override fun onBindViewHolder(holder: ToDoViewHolder, position: Int) {
        holder.bind(Todos[position])
    }

    // RecyclerView.Adapter 상속 시 무조건 override 해야하는 fun - 보통 Todos.size를 return, RecyclerView내의 item 개수 return하는 함수
    override fun getItemCount(): Int {
        return Todos.size
    }

    // Todos list의 각 item id return
    override fun getItemId(position: Int): Long {
        return position.toLong()
    }
}

 

 

(2) DoneFragment, PendingFragment 에 Adapter 연결

  RecyclerView Adapter를 생성했으니 RecyclerView를 xml에 포함하고 있는 DoneFragment, PendingFragment에서 adapter를 연동해보자. 두 Fragment의 코드 구조가 동일하므로 PendingFragment만 설명하겠다. 

 

1. view Binding 설정

 

2. Adapter 설정 (Adapter 인자는 List<Todo> 임에 주의)

=> binding!!.rvPending.adapter = adapter로 연동해준다.

 

3. RecyclerView에 꼭 Layout 설정해주기

=> 설정 안하면 skip 되어서 에뮬레이터에 아무것도 안뜨게 되므로 꼭 하기!

 

4. Fragment의 onCreateView는 View를 반환하므로 return binding.root 잊지말기 

 

1) PendingFragment.kt

package com.example.kotlintodo.ui.fragment

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.kotlintodo.adapter.TodoPendingAdapter
import com.example.kotlintodo.databinding.FragmentPendingBinding
import com.example.kotlintodo.model.Todo

class PendingFragment: Fragment() {

    private lateinit var binding: FragmentPendingBinding
    private lateinit var adapter : TodoPendingAdapter

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // 1. View Binding 설정
        binding = FragmentPendingBinding.inflate(inflater, container, false)

        // 2. adapter 설정 (list를 인자로)
        adapter = TodoPendingAdapter(listOf(Todo("first title", "first content", false)))
        adapter.setHasStableIds(true)
        binding!!.rvPending.adapter = adapter

        // 3. recyclerView에 Layout 꼭 설정하기 (안그러면 화면에 표시 안되고 skip됨)
        binding!!.rvPending.layoutManager = LinearLayoutManager(activity)

        // 4. return Fragment Layout View
        return binding.root
    }
}

 

2) DoneFragment.kt

package com.example.kotlintodo.ui.fragment

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.kotlintodo.adapter.TodoDoneAdapter
import com.example.kotlintodo.databinding.FragmentDoneBinding
import com.example.kotlintodo.model.Todo

class DoneFragment: Fragment() {

    private lateinit var binding: FragmentDoneBinding
    private lateinit var adapter : TodoDoneAdapter

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // 1. View Binding 설정
        binding = FragmentDoneBinding.inflate(inflater, container, false)

        // 2. adapter 설정 (list를 인자로)
        adapter = TodoDoneAdapter(emptyList<Todo>())
        adapter.setHasStableIds(true)
        binding!!.rvDone.adapter = adapter

        // 3. recyclerView에 Layout 꼭 설정하기 (안그러면 화면에 표시 안되고 skip됨)
        binding!!.rvDone.layoutManager = LinearLayoutManager(activity)

        // 4. return Fragment Layout View
        return binding.root
    }
}

 

 

 

(3) 여기까지 최종 결과

  PendingFragment에 예시로 넣은 item 한개가 보이는 것을 확인할 수 있다. 

 

 

*GitHub 링크

https://github.com/leesoeun98/KotlinToDo

 

GitHub - leesoeun98/KotlinToDo

Contribute to leesoeun98/KotlinToDo development by creating an account on GitHub.

github.com

 

Comments