안드로이드 학습(Kotlin)

14. RecyclerView와 Adapter, ViewHolder에 대해

리저브콜드브루 2025. 2. 4. 14:02
728x90
반응형

RecyclerView

RecyclerView는 Android에서 리스트그리드 형태의 데이터를 효율적으로 표시하기 위한 강력한 UI 컴포넌트이다.

  • RecyclerView는 리스트(ListView)의 개선된 버전으로, 대량의 데이터를 효율적으로 표시하기 위해 설계됨
  • 기존의 ListView보다 더 유연하고 성능이 뛰어남
  • 이름 그대로 View를 재활용(Recycler) 하여 메모리 사용을 최소화하고 성능을 최적화함

 

RecyclerView의 핵심요소 3가지

  • Adapter (어댑터): 
    • 데이터와 RecyclerView를 연결하는 브릿지 역할
    • 데이터 바인딩 및 ViewHolder 생성 관리
  • ViewHolder (뷰홀더):
    • 각 항목의 뷰를 재활용하기 위해 사용하는 객체
    • 뷰 찾기(findViewId) 성능 최적화
  • LayoutManager (레이아웃 매니저):
    • 아이템의 배치 방식을 정의 (수평, 수직, 그리드 등)
    • 예: LinearLayoutManager, GridLayoutManager, StaggeredGridLayoutManager

 

RecyclerView  vs ListView

비교 항목 RecyclerView ListView
성능 View 재활용 최적화, 더 나은 성능 View 재활용이 제한적, 성능 저하 가능
레이아웃 지원 수직, 수평, 그리드, 격자 등 다양한 레이아웃 지원 기본적으로 수직 리스트만 지원
애니메이션 기본적으로 애니메이션 지원 별도의 구현 필요
ViewHolder 패턴 강제 적용 선택 사항 (권장되지만 필수는 아님)
데이터 변경 처리 notifyItemInserted(), notifyItemRemoved() 등 세부 제어 가능 notifyDataSetChanged()로 전체 갱신

RecyclerView 기본 사용법

1. RecyclerView 추가 (XML)

XML 레이아웃 파일 (activity_main.xml)

<androidx.recyclerview.widget.RecyclerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/recyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp" />

 

2. ViewHolder 클래스 생성

간단한 name, age 요소를 보유하도록 클래스 생성

class UserViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    val nameTextView: TextView = itemView.findViewById(R.id.textName)
    val ageTextView: TextView = itemView.findViewById(R.id.textAge)
}

 

 

3. Adapter 클래스 생성

User라는 age, name 정보를 가진 데이터 모델이 있다고 가정하여 작성

class UserAdapter(private val userList: List<User>) :
    RecyclerView.Adapter<UserViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_user, parent, false)
        return UserViewHolder(view)
    }

    override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
        val user = userList[position]
        holder.nameTextView.text = user.name
        holder.ageTextView.text = "${user.age}세"
    }

    override fun getItemCount(): Int = userList.size
}
  • onCreateViewHolder(): 아이템 뷰 생성
  • onBindViewHolder(): 데이터 바인딩
  • getItemCount(): 리스트 항목 개수 반환

 

4. 아이템 레이아웃 추가 (item_user.xml)

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="12dp">

    <TextView
        android:id="@+id/textName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="18sp"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/textAge"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="14sp" />
</LinearLayout>

 

 

5. MainActivity (RecyclerView 연결)

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
        recyclerView.layoutManager = LinearLayoutManager(this)

        val users = listOf(
            User("Alice", 25),
            User("Bob", 30),
            User("Charlie", 28)
        )

        recyclerView.adapter = UserAdapter(users)
    }
}
  • LinearLayoutManager: 수직 리스트 형태로 배치
  • UserAdapter: 사용자 리스트를 RecyclerView에 연결

 

6. 아이템 클릭 이벤트 처리

RecyclerView에는 setOnItemClickListener가 기본적으로 존재하지 않으므로, Adapter에서 직접 처리해야 한다.

class UserAdapter(
    private val userList: List<User>,
    private val onItemClick: (User) -> Unit
) : RecyclerView.Adapter<UserViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_user, parent, false)
        return UserViewHolder(view)
    }

    override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
        val user = userList[position]
        holder.nameTextView.text = user.name
        holder.ageTextView.text = "${user.age}세"

        holder.itemView.setOnClickListener {
            onItemClick(user)
        }
    }

    override fun getItemCount(): Int = userList.size
}

MainActivity에서 클릭 이벤트 등록

recyclerView.adapter = UserAdapter(users) { user ->
    Toast.makeText(this, "${user.name} 클릭됨!", Toast.LENGTH_SHORT).show()
}

 

 

 

7. 데이터 변경 처리 (DiffUtil 활용)

리스트 데이터가 변경될 때 전체 리스트가 갱신되면 비효율적이다.

DiffUtil을 사용해 변경된 부분만 효율적으로 업데이트할 수 있다.

 

DiffUtil.ItemCallback 구현

class UserDiffCallback : DiffUtil.ItemCallback<User>() {
    override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
        return oldItem.name == newItem.name
    }

    override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
        return oldItem == newItem
    }
}

 

ListAdapter 사용

class UserAdapter : ListAdapter<User, UserViewHolder>(UserDiffCallback()) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_user, parent, false)
        return UserViewHolder(view)
    }

    override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
        val user = getItem(position)
        holder.nameTextView.text = user.name
        holder.ageTextView.text = "${user.age}세"
    }
}

 

데이터 갱신

val newList = listOf(User("David", 35), User("Eve", 29))
(userAdapter as ListAdapter<User, UserViewHolder>).submitList(newList)

submitList()를 호출하면 DiffUtil이 변경된 항목만 효율적으로 업데이트한다.

728x90
반응형