기본 RecyclerView / 멀티 뷰 타입 RecyclerView
구성 요소
item
: Display 할 여러 단어 중 하나의Word
object가 하나의item
에 해당한다.Adapter
: data를 RecyclerView 화면에 display할 준비를 한다.ViewHolder
: A pool of views. 리사이클러뷰가 display에 사용/재사용 하는 뷰
기본 RecyclerView
List에 있는 item들을 화면에 보여주는 가장 기본적인 RecyclerView를 만들어보자.
사진처럼 한 칸에 이미지 하나, 단어 하나가 들어가는 RecyclerView를 만들 것이다. (이미지는 안드로이드 스튜디오 기본 벡터 아이콘에서 아무거나 가져왔다 😅)
data class: Word.kt
Word라는 data class를 생성한다.
data class Word(
@StringRes val stringResourceId: Int,
@DrawableRes val imageResourceId: Int
)
Word
는 하나의 단어를 나타낸다.Word
인스턴스를 만들 때 문자열과 이미지 리소스 ID를 전달해야 한다.- 문자열 리소스 ID, 이미지 리소스 ID 모두 Int값이기 떄문에 호출 시 잘못된 순서롤 전달 할 수 있다. 이를 방지하기 위해 리소스 주석을 사용할 수 있다.
@StringRes
,@DrawableRes
를 설정하면 잘못된 유형의 리소스 ID가 들어오면 경고가 표시된다.
Datasource
화면에 표시 할 데이터는 외부 소스에서 가져올 수도 있지만 이번 실습에서는 drawable
과 values > string
디렉토리에 준비 된 데이터를 가져다 사용할 것이다.
Datasource
라는 클래스를 생성하고, 클래스 내부에 loadWords()
함수를 만든다.
class Datasource {
fun loadWords(): List<Word>{
return listOf<Word>(
Word(R.string.word1, R.drawable.ic_1),
Word(R.string.word2, R.drawable.ic_2),
Word(R.string.word3, R.drawable.ic_3),
Word(R.string.word4, R.drawable.ic_4),
Word(R.string.word5, R.drawable.ic_5)
)
}
}
loadWords()
는 Words 리스트를 반환한다.
activity_main.xml
리사이클러뷰를 표시할 액티비티의 xml은 다음과 같다.
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
</FrameLayout>
- 화면에 리사이클러뷰만 표시하므로 단일 하위 뷰를 갖는데 적합한
FrameLayout
을 사용했다. - LayoutManager는
LinearLayourManger
list_item.xml
하나의 item 화면을 구성한다. 레이아웃 폴더에 추가했다.
<?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="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/item_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="20dp"
tools:srcCompat="@drawable/ic_2" />
<TextView
android:id="@+id/item_word"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="@string/word1" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Adapter
class Adapter(
private val context: Context, private val dataset: List<Word>
) : RecyclerView.Adapter<Adapter.ItemViewHolder>() {
class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
val item_tv: TextView = view.findViewById(R.id.item_word)
val item_iv: ImageView = view.findViewById(R.id.item_image)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
val adapterLayout = LayoutInflater.from(parent.context)
.inflate(R.layout.list_item, parent, false)
return ItemViewHolder(adapterLayout)
}
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val item = dataset[position]
holder.item_tv.text = context.resources.getString(item.stringResourceId)
holder.item_iv.setImageResource(item.imageResourceId)
}
override fun getItemCount(): Int {
return dataset.size
}
}
Adapter
에는Word
목록을 매개변수로 전달한다.- 문자열과 이미지 리소스를 확인하는 방법에 대한 정보나 앱 관련 정보는
Context
객체 인스턴스에 저장된다. Adapter
클래스를 추상 클래스RecyclerView.Adapter
에서 확장하고, 꺾쇠 안에는 뷰 홀더 유형으로Adapater.ItemViewHolder
를 지정한다.
ㄴ ItemViewHolder
Adapter
클래스 내부에 ItemViewHolder
클래스를 생성한다. RecyclerView
는 뷰와 직접 상호작용하는 것이 아니라 ViewHolder
를 처리하기 때문.
class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
val item_tv: TextView = view.findViewById(R.id.item_word)
val item_iv: ImageView = view.findViewById(R.id.item_image)
}
Adapter
에서 Ctrl + I
를 눌러 구현해야 하는 메서드를 생성할 수 있다.
onCreateViewHolder()
, getItemCount()
, onBindViewHolder()
를 생성한다.
ㄴ onCreateViewHolder()
override fun onCreateViewHolder(
parent: ViewGroup, viewType: Int
): ItemViewHolder {
val adapterLayout = LayoutInflater.from(parent.context)
.inflate(R.layout.list_item, parent, false)
return ItemViewHolder(adapterLayout)
}
onCreateViewHolder()
는 레이아웃 관리자가 새 뷰 홀더를 만들 때 호출한다. 재사용할 수 있는 기존 뷰 홀더가 없는 경우parent
매개변수는 새 list item view가 child로 사용되는 상위 view group. 여기서 parent는 RecyclerViewviewType
매개변수는 여러개의 뷰 타입을 사용할 때 필요하다.
LayoutInflater
로 레이아웃 리소스list_item
을parent
뷰 그룹에 전달한다.- 마지막 매개변수
false
는attachToRoot = false
. 적절한 때에 리사이클러뷰가 이 item을 뷰 계층 구조에 추가하기 때문에.
ㄴ onBindViewHolder()
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val item = dataset[position]
holder.item_tv.text = context.resources.getString(item.stringResourceId)
holder.item_iv.setImageResource(item.imageResourceId)
}
- 레이아웃 관리자가 item 뷰의 컨텐츠를 바꾸기 위해 호출한다.
- 매개변수로 전달 된
position
을 기준으로Word
객체를 찾는다.
RecyclerView를 표시하는 MainActivity
onCreate()
메서드에 다음 코드를 추가한다.
val mDataset = Datasource().loadWords()
val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
recyclerView.adapter = Adapter(this, mDataset)
Datasource
인스턴스를 만들고loadWords()
메서드를 호출한다.recyclerView
변수에 레이아웃의 리사이클러뷰를 찾아 담는다.Adapter
인스턴스를 생성해recyclerView
에게 알린다.
완성 🤩
멀티 뷰 타입 RecyclerView
위에서 만든 리사이클러뷰를 멀티 뷰 타입을 사용하도록 수정해보자. 채팅 화면처럼 오른쪽, 왼쪽 두 개의 뷰가 사용되도록 만든다.
list_item_right.xml
먼저 추가 할 오른쪽 뷰의 레이아웃을 구성한다.
<?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="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="20dp"
android:gravity="right"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/item_right_image"
android:paddingRight="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:srcCompat="@drawable/ic_2" />
<TextView
android:id="@+id/item_right_word"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="@string/word1" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Word.kt 수정
data class Word(
@StringRes val stringResourceId: Int,
@DrawableRes val imageResourceId: Int,
val viewType: Int
){
companion object {
const val VIEW_TYPE_LEFT = 0
const val VIEW_TYPE_RIGHT = 1
}
}
- Word 인스턴스를 만들 때 뷰 타입도 전달되도록
viewType
변수를 추가한다. companion object
에 두 뷰 타입을 작성한다.
Datasource 수정
이제 Word 하나 생성할 때 뷰 타입도 전달하기 때문에 각 Word의 마지막 매개변수에 0 또는 1을 추가한다.
class Datasource {
fun loadWords(): List<Word>{
return listOf<Word>(
Word(R.string.word1, R.drawable.ic_1,0),
Word(R.string.word2, R.drawable.ic_2,1),
Word(R.string.word3, R.drawable.ic_3,0),
Word(R.string.word4, R.drawable.ic_4,1),
Word(R.string.word5, R.drawable.ic_5,0)
)
}
}
0
이 전달된 Word는 왼쪽에,1
이 전달된 Word는 오른쪽에 배치된다.
Adapter 수정
먼저 Adapter의 반환 유형을 다음과 같이 수정했다.
class Adapter(
private val context: Context, private val dataset: List<Word>
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
. . .
}
ㄴ ItemViewHolder() 수정
class ItemViewHolder1(private val view: View) : RecyclerView.ViewHolder(view) {
val item_tv: TextView = view.findViewById(R.id.item_word)
val item_iv: ImageView = view.findViewById(R.id.item_image)
}
class ItemViewHolder2(private val view: View) : RecyclerView.ViewHolder(view) {
val item_tv2: TextView = view.findViewById(R.id.item_right_word)
val item_iv2: ImageView = view.findViewById(R.id.item_right_image)
}
- 기존 view holder는
ItemViewHolder1
로 수정하고, 오른쪽 뷰를 담당할ItemViewHolder2
를 추가했다.
ㄴ onCreateViewHolder() 수정
override fun onCreateViewHolder(
parent: ViewGroup, viewType: Int
): RecyclerView.ViewHolder {
val adapterLayout: View?
return when(viewType){
Word.VIEW_TYPE_LEFT -> {
adapterLayout = LayoutInflater.from(parent.context)
.inflate(R.layout.list_item, parent, false)
ItemViewHolder1(adapterLayout)
}
Word.VIEW_TYPE_RIGHT -> {
adapterLayout = LayoutInflater.from(parent.context)
.inflate(R.layout.list_item_right, parent, false)
ItemViewHolder2(adapterLayout)
}
else -> throw RuntimeException("알 수 없는 뷰 타입")
}
}
- 전달된
viewType
에 따라 다른 뷰를 inflate하고, 뷰 타입에 맞는 뷰 홀더를 생성한다.
ㄴ onBindViewHolder() 수정
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val item = dataset[position]
when(item.viewType){
Word.VIEW_TYPE_LEFT -> {
(holder as ItemViewHolder1).item_tv.text = context.resources.getString(item.stringResourceId)
holder.item_iv.setImageResource(item.imageResourceId)
holder.setIsRecyclable(false)
}
Word.VIEW_TYPE_RIGHT -> {
(holder as ItemViewHolder2).item_tv2.text = context.resources.getString(item.stringResourceId)
holder.item_iv2.setImageResource(item.imageResourceId)
holder.setIsRecyclable(false)
}
else -> throw RuntimeException("알 수 없는 뷰 타입")
}
}
ㄴ getItemViewType() 추가
position
위치에 있는 Word
인스턴스의 viewType
을 반환한다.
override fun getItemViewType(position: Int): Int {
return dataset[position].viewType
}
Layout Backgroud
리사이클러뷰 칸이 잘 구분되어 보이게 색깔이 있는 상자에 Word를 넣었다.
layout_border.xml
이 xml 파일을 새로 생성하고, list_item.xml
과 list_item_right.xml
에 background
속성을 추가했다.
<?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="#9DB1F6"/>
<stroke
android:width="3dp"
android:color="@color/white"/>
</shape>
끝 😊
참고
Android Developers Codelab
멀티 뷰 타입 리사이클러뷰
Author And Source
이 문제에 관하여(기본 RecyclerView / 멀티 뷰 타입 RecyclerView), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@emily2307/RecyclerView저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)