Kotlin

[Kotlin] RecyclerView 사용 방법

cob 2022. 10. 31. 18:09

 

 

 

RecyclerView 

 

RecyclerView 란?
아이템 뷰를 계속해서 재활용하는 방법이다. findViewById(R.id.이름) 와 같이 xml 리소스에 접근하는 비용이 큰 함수를 사용한다면 성능 저하를 야기할 수 있습니다. 대신에 뷰 홀더 클래스를 만들고 안에 변수를 선언하면 뷰에 즉시 액세스가 가능해진다. 뷰 홀더 내부에서는태그를 이용해 findViewId를 대체한다. (변수 선언 외에 딱히 해줄 것이 없다)

 

 

 

 

1.  사용 방법

1-1) Item View 생성

List 하나에 대한 아이템 뷰를 생성한다.

Item View 경로

  • res > layout 마우스 우측 > NEW > Layout Resource File 생성

 

(list_item.xml)

  • item에 대한 height, width 설정 (width는 match_parent로 설정하여 부모의 폭에 맞춘다.)

item view

 

 

1-2) 객체 모델 생성

Model

  • app > java > 패키지 경로 > Kotlin File/Class 생성
  • item의 layout을 넣을 수 있게 객체를 생성한다.
  • RecyclerView 연동을 하면 사용 가능
// 이미지 리소스들을 셋을 해줄 때는 Int형 값으로 넣어줘야한다.
class Profiles(val gender:Int, val name:String, val age:Int, val job:String )

 

 

 

1-3) Adpater 생성

( Adapter 기본 틀 )

class TodoRecyclerViewAdapter (private val todoList : ArrayList<TodoEntity>)
    : RecyclerView.Adapter<TodoRecyclerViewAdapter.MyViewHolder>(){             // 1)
        inner class MyViewHolder(binding : ItemTodoBinding) :                   // 2)
            RecyclerView.ViewHolder(binding.root) {
            val tv_importance = binding.tvImportance
            val tv_title = binding.tvTitle

            /**
             * 뷰 바인딩에서 기본적으로 제공하는 root 변수는 레이아웃의
             * 루트 레이아웃을 의미한다.
             */
            val root = binding.root
        }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder{// 3)
        TODO("Not yet implemented")
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {            // 4)
        TODO("Not yet implemented")
    }

    override fun getItemCount(): Int {                                              // 5)
        TODO("Not yet implemented")
    }
}

1) 어댑터 객체를 생성할 때 List를 인수로 받아준다.  RecyclerView.Adapter<  2) 에서 만든 뷰 홀더 클래스>를 상속한다.(뷰 홀더 패턴으로 구현)
2) MyViewHolder 클래스를 생성, RecyclerView.ViewHolder 클래스를 상속한 뷰 홀더 클래스
3) onCreateViewHolder() : 2)에서 만든 뷰홀더 객체를 생성
4) onBindViewHolder() : 1)에서 받은 데이터를 3)에서 생성한 뷰 홀더 객체를 어떻게 넣어줄지를 결정한다.
5) getItemCount() : 데이터가 몇 개인지 변환해주어야 한다..

뷰 홀더 패턴 이란?
뷰 객체를 뷰 홀더에 보관하여 반복적인 메서드 호출을 줄여 속도를 개선하는 패턴이다.

 

(onCreateViewHolder, onBindViewHolder,  getItemCount 구현)

	
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { // 3)
          // item_todo.xml 뷰 바인딩 객체 생성
          val binding: ItemTodoBinding =
              ItemTodoBinding.inflate(LayoutInflater.from(parent.context), parent, false)
          return MyViewHolder(binding)
  }   
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
    val todoData = todoList[position]
    // 중요도에 따른 생상을 변경
    when (todoData.importance) {
        1 -> holder.tv_importance.setBackgroundResource(R.color.red)
        2 -> holder.tv_importance.setBackgroundResource(R.color.yellow)
        3 -> holder.tv_importance.setBackgroundResource(R.color.green)
    }
    // 중요도에 따라 텍스트(1, 2, 3) 변경
    holder.tv_importance.text = todoData.importance.toString()
    // 할 일의 제목 변경
    holder.tv_title.text = todoData.title
}

override fun getItemCount(): Int {
    // 리사이클러뷰 아이템 개수는 할 일 리스트 크기
    return todoList.size
}

 

 

 

1-4) Activity에서 어댑터 연결

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

    private lateinit var db : AppDatabase
    private lateinit var todoDao : ToDoDao
    private lateinit var todoList: ArrayList<TodoEntity>

    private lateinit var adapter: TodoRecyclerViewAdapter // 1)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.btnAdd.setOnClickListener {
            val intent = Intent(this, AddTodoActivity::class.java)
            startActivity(intent)
        }

        // DB 인스턴스를 가져오고 DB작업을 할 수 있는 DAO를 가져옵니다.
        db = AppDatabase.getInstance(this)!!
        todoDao = db.getTodoDAO()

        getAllTodoList()    // 할 일 리스트 가져오기
    }

    private fun getAllTodoList() {
        Thread {
            todoList = ArrayList(todoDao.getAll())
            setRecyclerView()
        }.start()
    }
    private fun setRecyclerView(){                              // 2)
        // 리사이클러뷰 설정
        runOnUiThread{
            adapter = TodoRecyclerViewAdapter(todoList) // 어댑터 객체 할당
            binding. recyclerView.adapter = adapter
            // 리사이클러뷰 어댑터로 위에서 만든 어댑터 설정
            binding.recyclerView.layoutManager = LinearLayoutManager(this)
            // 레이아웃 매니저 설정
        }
    }

    override fun onRestart() {
        super.onRestart()
        getAllTodoList()        // 3)
    }
}

1) 리사이클러뷰 어댑터를 변수로 선언
2) setRecyclerView() 함수 생성, UI 관련 작업이므로 runOnUiThread{}를 사용해 코드를 UI스레드에서 실행합니다.(왜냐하면 setRecyclerView 함수를 부르는 곳이 getAllTodoList 함수인데, 이 함수가 백그라운드 스레드에서 실행되기 때문입니다.)
3) onRestart() 함수는 액티비티가 멈췄다가 다시 시작할 때 실행되는 함수입니다. getAllTodoList() 함수를 작동시켜 새롭게 할 일 리스트 데이터를 갱신시켜 줍니다. 이렇게 하면 addTodoAcitiviy에서 다시 돌아왔을 때 리스트가 갱신됩니다.

 

 

1-5) Click Inteface 구현

Click Inteface 구현

// 길게 클릭시 이벤트
interface OnItemLongClickListener {
    fun onLongClick(position : Int)
}

 

 

1-6) Adapter에 클릭 이벤트 적용

class TodoRecyclerViewAdapter (private val todoList : ArrayList<TodoEntity>,
private val listener : OnItemLongClickListener) :            // 1)
RecyclerView.Adapter<TodoRecyclerViewAdapter.MyViewHolder>(){
		...

	override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
			...
			holder.root.setOnLongClickListener {                   // 2)
            listener.onLongClick(position)
            false
	     }
}

1) onItemLongClickListener 인터페이스 구현체는 메인 액티비티에서 넘겨주어야 합니다.
2) holder.root는 한 아이템 뷰의 루트 레이아웃입니다. setOnLongClickListener() 함수를 사용하여 길게 눌렸을 때 어떤 코드가 실행될지 적어주면 됩니다. 해당 코드에서는 1)에서 넘겨진 OnItemLongClickListener 구현체의 onLongClick() 함수를 실행해주었습니다. 인수로 position을 넘겨주어 어떤 것이 눌렸는지 리스너에게 알려줍니다.

 

 

1-7) Activity에서 OnItemLongClickListener 인터페이스를 구현한 후 리사이클러뷰에 넘겨주기

class MainActivity : AppCompatActivity(), OnItemLongClickListener { // 1)
		...

	private fun setRecyclerView(){
		runOnUiThread{
            adapter = TodoRecyclerViewAdapter(todoList, this)       // 2)
            binding. recyclerView.adapter = adapter
            binding.recyclerView.layoutManager = LinearLayoutManager(this)
        }
	}
	/**
   * OnItemLongClickListener 인터페이스 구현
   */
  override fun onLongClick(position: Int) {                          // 3)
      val builder : AlertDialog.Builder = AlertDialog.Builder(this)
      builder.setTitle("할 일 삭제")                          // 제목 설정
      builder.setMessage("정말 삭제하시겠습니까?")             // 내용 설정
      builder.setNegativeButton("취소", null)     // 취소 버튼 설정
      builder.setPositiveButton("네", 
          object : DialogInterface.OnClickListener {
              override fun onClick(p0: DialogInterface?, p1: Int) {
                  deleteTodo(position)
              }
          }
      )
      builder.show()
  }
  
  private fun deleteTodo(position: Int) {                               // 4)
      Thread {
          todoDao.deleteTodo(todoList[position])              // DB에서 삭제
          todoList.removeAt(position)                         // 리스트에서 삭제
          runOnUiThread { 
              adapter.notifyDataSetChanged()
              Toast.makeText(this, "삭제되었습니다.", Toast.LENGTH_SHORT).show()
          }
      }.start()
  }
}

1) OnItemLongClickListener 인터페이스를 구현해줍니다.
2) 리사이클러뷰 어댑터에서 리스트와 OnItemLongClickListener 구현체를 인수로 받습니다. 이미 해당 인터페이스를 메인 액티비에 구현해주고 있기 때문에 this로 넘겨줍니다.
3) OnItemLongClickListener에서 구현해야 하는 onLongClick() 함수를 구현해줍니다. 길게 눌렀을 때 대화 상자가 뜨도록 하고, 알림 창 alert Dialog는 진행하기 전에 사용자에게 의사를 물어볼 때 사용합니다. 각 버튼에 클릭 리스너를 달 수 있습니다. [네] 버튼(Positive Button)을 누르면 deleteTodo() 함수를 호출해 삭제가 진행됩니다.
4) deleteTodo() 함수에서는 Room 데이터베이스에서 해당할 일을 삭제하고 todoList를 업데이트합니다. adapter.notifyDataSetChanged() 함수는 어댑터에게 데이터가 바뀌었음을 알려줌으로써 리사이클러뷰가 그에 맞춰 자동으로 업데이트되게 합니다

반응형