SW 공부노트
[안드로이드/Kotlin] RecyclerView 정리(Adapter, 클릭 리스너) 본문
Recyclerview
RecyclerView는 ListView의 확장된 개념으로 더 효율적인 기능이 추가되어 있다.
RecyclerView는 ViewHolder를 통해 만든 객체를 재사용한다는 큰 장점이 있다.
RecyclerView는 다음과 같이 구성된다.
- 아이템: 표시할 리스트의 단일 데이터 항목, 주로 데이터를 담은 클래스의 객체 하나를 나타낸다.
- Adapter: RecyclerView에 데이터를 연결하는 역할을 한다. 아이템 레이아웃과 데이터를 실제로 연결한다.
- ViewHolder: 어떤 데이터를 어디에 넣을 건지 , 데이터가 틀 안에 들어갈 수 있게 하는 기능 정의
- LayoutManager: 아이템의 배치를 담당한다. (Linear, Grid 등)
- ItemDecoration / ItemAnimation / Click Detection( onClickListener를 통해 직접 구현)
Adapter 구조
RecyclerView를 위한 Adapter는 여러 상위 어댑터 클래스를 상속할 수 있다.
- RecyclerView.Adpater<ViewHolder 객체>
- ListAdapter<데이터 객체, ViewHolder 객체>(DiffUtil)
위 클래스들을 상속하는 Adapter 클래스는 다음과 같이 작성하면 된다.
아래 예시는 클릭 리스너를 인수로 받을 때 사용할 수 있다.
class RecyclerAdapter():RecyclerView.Adapter<Item, RecyclerAdapter.ViewHolder>{
}
class RecyclerAdapter(private val onItemClicked: (Item) -> Unit)
:ListAdapter<Item, RecyclerAdapter.ViewHolder>(DiffUtil) {
}
위와 같은 어댑터를 상속할 때 공통적으로 작성해야 하는 재정의 메서드 및 클래스가 있다.
- onCreateViewHoler 메서드
ViewHolder를 반환하는 메서드로, 레이아웃을 inflate한다.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WordViewHolder {
val view = LayoutInflater.from(context).inflate(R.layout.item_recycler_ex,parent,false)
return ViewHolder(view)
}
* LayoutInflater 사용 / 아이템 뷰 바인딩을 사용하면 Binding클래스.Inflate 사용
- onBindViewHolder 메서드
ViewHolder를 생성한 후 데이터를 연결(바인딩)하는 메서드이다.
position을 통해 데이터 리스트 내 해당 위치의 데이터를 불러와 ViewHolder에 연결한다.
override fun onBindViewHolder(holder: WordViewHolder, position: Int) {
val current = getItem(position)
holder.bind(current.word)
}
- ViewHolder 클래스
각각의 뷰를 보관하는 Holder 객체이다. ItemView를 재활용하기 위해 각 요소를 저장해 사용한다.
아이템 생성 시 뷰 바인딩은 한 번만 하며, 그 바인딩된 객체를 가져다 사용하여 성능 부분에서 효율적이다. 즉, 뷰에 데이터를 연결(바인딩)하는 코드가 포함된다.
class WordViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val wordItemView: TextView = itemView.findViewById(R.id.textView)
fun bind(text: String?) {
wordItemView.text = text
}
}
* 인수로 itemView 대신 아이템 파일의 바인딩 변수를 받기도 한다. binding.root를 통해 View로 인수를 전달해야 함!
* 추가적으로 ListAdapter의 경우 DiffUtil 객체를 선언해 DiffUtil.ItemCallback을 구현해야 한다. 예제 코드는 아래 ListAdapter 파트에서 다루고 있다.
실행 순서
- UI 클래스에서 사용하기 위해 RecyclerView, Adapter 선언 -> adapter, LayoutManager 설정
- Adapter 에 데이터 삽입-> add(RecyclerView.adpater), submitList(ListAdapter), Adpater 내 등
- Adapter 통해 데이터를 RecyclerView에 연결해 출력
- 데이터 리스트의 position을 통해 그에 맞는 ViewHolder에 연결
ListAdapter 특징
ListAdapter는 submitList() 메서드로 리스트를 전달받아, AsyncListDiffer가 동작해 리스트의 차이를 계산하고 변화를 처리한다. 이를 위해 DiffUtil.ItemCallback을 구현해야 한다. 여기서는 아이템이 같은지 확인하고 만약 같을 경우 아이템의 세부 내용(contents)도 같은지 확인하여 다른 경우 이 아이템은 갱신의 대상이 된다.
- areItemsTheSame: 두 객체가 같은지 식별하기 위해 고유 식별자(ex. id)를 통해 확인한다.
- areContentsTheSame: areItemsTheSame이 True이면 호출되는 메서드로, 두 아이템의 값이 같은지 확인한다. 같은 아이템이어도 다른 값을 가지고 있는 경우가 있을 수 있기 때문!
* 재정의해야 하는 메서드가 2개 이상이기 때문에 람다식으로 표현하지 못하고 object를 상속하는 방식으로 구현한다.
companion object{
private val DiffUtil = object: DiffUtil.ItemCallback<Item>(){
override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean {
return oldItem == newItem
}
}
}
기본적으로 해당 라이브러리는 Room이나 API를 통해 새로운 비동기 리스트를 매번 갱신받아 갱신하는 경우를 가정한다.
즉, 리스트의 주소(참조)가 계속 바뀐다는 것을 가정한다.
실제로 참조가 같은 경우 더 이상 동작하지 않게 구현되어 있다.
그러므로 매번 Room이나 API를 통해 데이터를 가져오는 작업이 아니라 앱 내에서 참조가 변경되지 않는 하나의 리스트를 유지하면서 값이 변경될 경우에도 반영하기 위해서는 submitList 함수 호출 시 주소가 다른 리스트를 넘겨주어야 한다.
코틀린에서는 다음과 같이 간단하게 이를 구현할 수 있다.
.observe(this, Observer {
adapter.submitList(it?.toMutableList())
})
리스트 컬렉션에 toMutableList를 활용할 경우 새로운 내부 목록은 똑같은 새로운 참조 리스트가 반환된다.
클릭 리스너 구현
- ViewHolder 클래스 내에서 처리
아이템 별 클릭 리스너를 받아야 하기 때문에 ViewHolder 클래스의 bind 함수 안에 클릭 리스너를 작성해줘야 한다.
RecyclerView.ViewHolder 클래스 내 itemView를 사용해 각 아이템에 접근할 수 있다.
fun bind(movie: Movie){
title.text = movie.title
date.text = movie.date
itemView.setOnClickListener {
Log.d(Tag, movie.toString())
}
}
- 클릭 리스너를 어댑터 인수로 받아 처리
아이템 클릭 시 실행할 코드를 어댑터에 람다식으로 전달해 처리하는 방식
class RecyclerViewAdpater(private val clickListener: (Item) -> Unit): ListAdapter<Item, ViewHolder>(DiffUtil){
...
override fun onBindViewHolder(holder: MovieViewHolder, position: Int) {
Log.d(Tag, position.toString() + " check")
val item = getItem(position)
holder.bind(item)
holder.itemView.setOnClickListener {
clickListener(item)
}
}
}
// adapter 선언 시 리스너 코드 작성
val adapter = RecyclerViewAdapter({movie -> Log.d(TAG, movie.toString())})
Reference
https://velog.io/@24hyunji/AndroidKotlin-RecyclerView-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0
[Android/Kotlin] RecyclerView 사용해보기
RecyclerView 란?
velog.io
https://yunaaaas.tistory.com/43
[Android/Kotlin] RecyclerView 만들기
오늘은 간단한 리사이클러뷰 시리즈 1탄인 RecyclerView 만드는 방법에 대해 소개해보고자 합니다! RecyclerView 란?! RecyclerView란 ? 데이터 집합들을 각각의 개별 아이템 단위로 구성하여 화면에 출력해
yunaaaas.tistory.com
- ListAdapter submitList
https://bb-library.tistory.com/257
[안드로이드] ListAdapter의 작동 원리 및 갱신이 안되는 경우
개요 RecyclerView를 활용하여 목록을 리스팅할 때 흔히 사용하는 어답터로 RecyclerView.Adapter와 ListAdapter로 나뉜다. 전자는 아이템 목록을 직접 관리하며 값이 변경될 경우 변경된 범위, 항목에 대해
bb-library.tistory.com
'안드로이드 > 안드로이드 공부' 카테고리의 다른 글
[안드로이드/Kotlin] Binding(View, Data) (0) | 2023.04.12 |
---|---|
[안드로이드/Kotlin] Navigation (with 프래그먼트) (0) | 2023.04.12 |
저장소 패턴(Repository pattern) (0) | 2023.04.05 |
[안드로이드] 활동 수명 주기 및 상태 (0) | 2023.03.13 |
레이아웃(Layout) 정리 (0) | 2023.01.27 |