Glion 의 안드로이드 개발노트
[Android] RecyclerView 이해하기 (1) 본문
안드로이드에서 다량의 데이터를 표시할때, 리사이클러뷰를 많이 사용한다.
그동안 아무생각없이 사용했던 리사이클러뷰에 대해 확실하게 정리할 것이다.
안드로이드 개발자 문서에는 다음과 같이 설명하고 있다.
RecyclerView를 사용하면 대량의 데이터 세트를 효율적으로 표시할 수 있습니다. 개발자가 데이터를 제공하고 각 항목의 모양을 정의하면 RecyclerView 라이브러리가 필요할 때 요소를 동적으로 생성합니다.
이름에서 알 수 있듯이 RecyclerView는 이러한 개별 요소를 재활용합니다. 항목이 스크롤되어 화면에서 벗어나더라도 RecyclerView는 뷰를 제거하지 않습니다. 대신 RecyclerView는 화면에서 스크롤된 새 항목의 뷰를 재사용합니다. 이렇게 뷰를 재사용하면 앱의 응답성을 개선하고 전력 소모를 줄이기 때문에 성능이 개선됩니다.
리사이클러뷰는 Android Jetpack의 구성요소로서, 화면에서 벗어난 항목을 재활용하여 새로운 항목으로 이용한다.
리사이클러뷰의 원리에 대해서는 다음 글에서 잘 설명이 되어있다.
https://wooooooak.github.io/android/2019/03/28/recycler_view/
RecyclerView는 데이터에 해당하는 뷰가 포함된 ViewGroup로서, 이는 뷰 자체이므로 다른 UI요소를 추가할 때처럼 <RecyclerView></RecyclerView>로 xml layout 파일에 추가할 수 있다.
RecyclerView의 구현단계는 다음과 같다.
1. 먼저 나타내고자 하는 목록의 모양을 결정한다. 세로 리스트 형태인지, 가로 리스트 형태인지, 그리드 형태로 나타낼지 등등을 정한다.
이는 LayoutManager클래스를 통해 나타낼 수 있고, LinearLayoutManager, GridLayoutManager, StaggeredGridLayoutManager 3가지 종류가 있다.
2. 각 요소가 나타내는 모양과 동작에 대해 설계한다.
3. 아이템의 모양을 담아 보관할 ViewHolder 클래스를 작성한다.
4. 데이터를 RecyclerView 와 연결하는 Adapter를 정의한다. Adapter정의시에는 onCreateViewHolder, onBindViewHolder, getItemCount 메소드를 오버라이드 해야 한다.
간단한 리사이클러뷰를 만들어보자.
간단하게 만들 리스트는 1부터 100까지의 이름을 가진 리스트 이다. 각 요소를 클릭하면 Toast 메세지로 이름을 띄워준다.
각 요소가 나타낼 모양 layout을 먼저 만든다.
layout 폴더에 item_temp_recycler.xml이라는 이름으로 만들어 주었다
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginVertical="5dp">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
app:layout_constraintHorizontal_bias="0.05"
android:layout_height="wrap_content"
android:textAlignment="center"
android:text="@string/temp_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
android:id="@+id/tv_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:text="@string/temp_content"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
아래와 같은 모양이 된다.
TextView의 text는 string.xml에 title과 content를 지정해주었다. 각 내용은 다음과 같다.
<string name="temp_title">[%d]</string>
<string name="temp_content">항목은 [%d] 입니다</string>
1부터 100까지의 항목을 주기 위해 형식지정자를 사용하여 표현하였다.
이제 만들어준 레이아웃을 기억할 ViewHolder 클래스를 만든다. 따로 만들어주어도 상관 없지만, Adapter의 인자를 사용하기 위해 inner class로 만들어 주었다.
ViewHolder에서 기억하는 모양을 유지한채 추후에 필요한 데이터만 교체해 줄 것이다.
class TempRecyclerViewAdapter(private val itemList: ArrayList<Int>, val mContext: Context): RecyclerView.Adapter<TempRecyclerViewAdapter.ViewHolder>() {
inner class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView){
val title: TextView = itemView.findViewById(R.id.tv_title)
val content: TextView = itemView.findViewById(R.id.tv_content)
}
이제 Adapter를 만들어준다. RecyclerView에 사용될 Adapter는 RecyclerView.Adapter를 상속받고, 제네릭타입으로 ViewHolder가 들어간다.
Adapter에는 onCreateViewHolder, getItemCount, onBindViewHolder를 필수적으로 오버라이딩 해야한다
class TempRecyclerViewAdapter(private val itemList: ArrayList<Int>, val mContext: Context): RecyclerView.Adapter<TempRecyclerViewAdapter.ViewHolder>() {
// 뷰홀더 클래스
inner class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView){
val title: TextView = itemView.findViewById(R.id.tv_title)
val content: TextView = itemView.findViewById(R.id.tv_content)
}
// 화면에 띄울 뷰 홀더 객체 생성
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_temp_recycler, parent, false)
return ViewHolder(view)
}
// 어댑터 생성시 제일 먼저 실행됨 - 아이템의 총 개수 반환
override fun getItemCount(): Int {
return itemList.size
}
// 뷰 홀더에서 기억한 아이템에 데이터만 Bind
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.title.text = mContext.getString(R.string.temp_title).format(position+1)
holder.content.text = mContext.getString(R.string.temp_title).format(position+1)
holder.content.setOnClickListener {
Toast.makeText(mContext, "항목 ${position+1} 선택", Toast.LENGTH_SHORT).show()
}
}
}
Adapter 클래스의 인자로는 아이템을 가지고있는 ArrayList가, getString을 사용하기 위한 context 가 들어온다. 아이템을 가지고있는 ArrayList는 반드시 ArrayList가 아니여도 되지만, 어떤 형태로던 존재해야 한다.
다음으로 각 함수에 대해서 설명하자면,
onCreateViewHolder는 이전에 작성한 레이아웃을 inflate하여 view 형태로 ViewHolder 객체를 생성해준다.
한 화면에 띄울수 있는 항목이 5개라면, 여유있게 7~8개 정도의 항목을 생성하여 재활용 하여 사용한다.
getItemCount 는 Adapter가 생성되면 제일 먼저 실행되는 함수로, 리스트로 생성해야 하는 아이템들의 총 개수를 반환한다.
onBindViewHolder 는 position에 따라 item의 데이터를 Bind해준다. 또한 각 항목 클릭됬을때의 이벤트 setOnClickListener를 넣어주어 클릭했을때 Toast를 띄워주게 하였다.
이제 MainActivity로 넘어와서 RecyclerView에 Adapter를 붙여주면 완성이다.
class MainActivity : AppCompatActivity(), View.OnClickListener {
private lateinit var mRecyclerView: RecyclerView
private lateinit var mContext: Context
private lateinit var mItemList: ArrayList<Int>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 아이템 준비
mItemList = ArrayList()
for(i:Int in 1..100)
mItemList.add(i)
mContext = this
mRecyclerView = findViewById(R.id.rv_temp) // 레이아웃에 RecyclerView의 id
mRecyclerView.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false)
mRecyclerView.adapter = TempRecyclerViewAdapter(mItemList, mContext)
}
}
mItemList를 만들어 RecyclerViewAdapter에 전달해주었다.
RecyclerView의 구조와 간단한 사용방법에 대해 알아보았다.
다음 글에서는 interface를 생성하여 Adapter에 있는 pos와 같은 값을 MainActivity에서 사용할 수 있게끔 해 볼 것이다.