기본 RecyclerView / 멀티 뷰 타입 RecyclerView
구성 요소

item: Display 할 여러 단어 중 하나의Wordobject가 하나의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.)