본문 바로가기
안드로이드

[안드로이드] 리사이클러뷰 데이터 바인딩 (RecyclerView data binding)

by algosketch 2022. 1. 15.

https://github.com/android/architecture-samples

 

GitHub - android/architecture-samples: A collection of samples to discuss and showcase different architectural tools and pattern

A collection of samples to discuss and showcase different architectural tools and patterns for Android apps. - GitHub - android/architecture-samples: A collection of samples to discuss and showcase...

github.com

이 프로젝트를 보고 해석하여 작성한 글이다. 일부 코드는 위 프로젝트와 다르게 작성되었다. 리사이클러뷰 데이터 바인딩은 조금 복잡하다. (정말?) 다음과 같은 과정으로 진행된다. Activity 와 ViewModel 을 바인딩하는 방법은 이미 알고 있을 거라 가정하며 자세하게 설명하지 않고 코드 위주로 설명한다.

  1. 프로젝트 세팅
  2. ViewModel 및 더미 데이터 생성
  3. MainActivity 와 ViewModel 연결
  4. Adapter 및 ViewHolder stub 구현
  5. Binding 함수 구현
  6. RecyclerView 배치 및 더미데이터 연결
  7. RecyclerView 의 item layout 에 데이터 바인딩
  8. ViewHolder 의 factory method 구현 및 bind 함수 구현
  9. RecyclerView.Adapter 구현
  10. MainActivity 에서 Adapter 연결

 

1. 프로젝트 세팅

ViewModel 과 데이터 바인딩을 위해 모듈 수준의 build.gradle 에 다음을 추가한다.

apply plugin: 'kotlin-kapt'
android {
    dataBinding {
        enabled = true
    }
}
dependencies {
    implementation "androidx.fragment:fragment-ktx:1.4.0"
}

 

2. ViewModel 및 더미데이터 생성

String 만 갖고 있는 간단한 데이터 클래스를 사용한다.

data class ExampleItem(val content: String)
class MainViewModel : ViewModel() {
    val dummyList = listOf(
        ExampleItem("아무말1"),
        ExampleItem("아무말2"),
        ExampleItem("아무말3"),
        ExampleItem("아무말4"),
        ExampleItem("아무말5"),
    )
}

 

 

3. MainActivity 와 ViewModel 연결

<?xml version="1.0" encoding="utf-8"?>
<layout>
    <data>
        <variable
            name="viewModel"
            type="org.algosketch.datebindingtest.MainViewModel" />
    </data>

    <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="match_parent"
        tools:context=".MainActivity">

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

 

 

4. Adapter 및 ViewHolder stub + @

class ExampleAdapter : RecyclerView.Adapter<ExampleAdapter.ExampleViewHolder>() {
    lateinit var exampleList: List<ExampleItem>

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ExampleViewHolder {
        TODO("Not yet implemented")
    }

    override fun onBindViewHolder(holder: ExampleViewHolder, position: Int) {
        TODO("Not yet implemented")
    }

    override fun getItemCount(): Int {
        TODO("Not yet implemented")
    }

    class ExampleViewHolder(view: View) : RecyclerView.ViewHolder(view) {

    }
}

RecyclerView 에서 사용할 list 를 만들어 준다.

 

5. Binding 함수 구현

@BindingAdapter("app:items")
fun setList(recyclerView: RecyclerView, items: List<ExampleItem>?) {
    items?.let {
        (recyclerView.adapter as ExampleAdapter).exampleList = items
    }
}

여기서부터 RecyclerView 를 바인딩하기 위한 코드가 시작된다. 이 함수는 RecyclerView 에 연결할 리스트를 연결하기 위한 용도로 사용된다. RecyclerView 를 사용하는 xml 파일에서 app:items 속성으로 대입할 수 있다. "여기에 들어갈 값은 아무렇게나 넣어도 된다."

 

6. RecyclerView 배치 및 더미데이터 연결

<androidx.recyclerview.widget.RecyclerView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
    android:orientation="vertical"
    app:items="@{viewModel.dummyList}" />

2번에서 ViewModel 에 만들어둔 더미 데이터를 5번에서 만든 바인딩 함수를 이용하여 데이터를 바인딩 시켜준다.

 

7. RecyclerView 의 item layout 에 데이터 바인딩

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
        <variable
            name="item"
            type="org.algosketch.datebindingtest.ExampleItem" />
    </data>
    
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="16dp">
        
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toTopOf="parent"
            tools:text="여기 적힌 내용이 토스트 메시지로 출력될 거야!"
            android:text="@{item.content}"/>

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

tools:text 는 미리보기 화면에서만 보이는 내용이니 신경쓰지 않아도 된다. 중요한 건 android:text="@{item.content}" 와 같은 방식으로 데이터를 바인딩할 수 있다는 것이다. xml 에서 사용할 변수는 ViewModel 과 같은 방식으로 선언할 수 있다. 바로 다음인 8번에서 바인딩 함수를 구현할 것이다.

 

8. ViewHolder 의 factory method 구현 및 bind 함수 구현

class ExampleAdapter : RecyclerView.Adapter<ExampleAdapter.ExampleViewHolder>() {
    lateinit var exampleList: List<ExampleItem>

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ExampleViewHolder {
        TODO("Not yet implemented")
    }

    override fun onBindViewHolder(holder: ExampleViewHolder, position: Int) {
        TODO("Not yet implemented")
    }

    override fun getItemCount(): Int {
        TODO("Not yet implemented")
    }

    class ExampleViewHolder private constructor(val binding: ItemExampleBinding) : RecyclerView.ViewHolder(binding.root) {
        fun bind(item: ExampleItem) {
            binding.item = item
            binding.executePendingBindings()
        }
        
        companion object {
            fun from(parent: ViewGroup) : ExampleViewHolder {
                val layoutInflater = LayoutInflater.from(parent.context)
                val binding = ItemExampleBinding.inflate(layoutInflater, parent, false)

                return ExampleViewHolder(binding)
            }
        }
    }
}

companion object 로 생성자를 호출하여 반환하는 from 메서드를 구현한다. 앞으로 ViewHolder 는 이 메서드를 통해서만 생성되므로 주 생성자는 private 으로 막아둔다. 생성자에는 binding 객체가 들어가고, 이 객체를 통해서 bind 함수를 구현한다. bind 함수는 매우 간단하다. 바인딩 해야하는 View 의 수가 늘어도 바뀌지 않을 것이다.

executePendingBindings() 메소드는 지금 상황에선 없어도 되지만, 렌더링 이후에 바뀌는 값이 있다면 바뀐 부분이 적용되지 않을 수 있다. 그때 이 메소드를 사용해주면 해결된다.

 

9. RecyclerView.Adapter 구현

class ExampleAdapter : RecyclerView.Adapter<ExampleAdapter.ExampleViewHolder>() {
    lateinit var exampleList: List<ExampleItem>

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ExampleViewHolder {
        return ExampleViewHolder.from(parent)
    }

    override fun onBindViewHolder(holder: ExampleViewHolder, position: Int) {
        holder.bind(exampleList[position])
    }

    override fun getItemCount() = exampleList.size

    class ExampleViewHolder private constructor(val binding: ItemExampleBinding) : RecyclerView.ViewHolder(binding.root) {
        fun bind(item: ExampleItem) {
            binding.item = item
        }

        companion object {
            fun from(parent: ViewGroup) : ExampleViewHolder {
                val layoutInflater = LayoutInflater.from(parent.context)
                val binding = ItemExampleBinding.inflate(layoutInflater, parent, false)

                return ExampleViewHolder(binding)
            }
        }
    }
}

Adapter 코드를 구현하며 마무리한다. (끝인줄 알았는데 깜빡하고 아답터 연결을 안 해줬다.) Adapter 코드는 매우 간단하여 설명이 필요없다.

 

10. MainActivity 에서 Adapter 연결

class MainActivity : AppCompatActivity() {
    ...
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        
        setupAdapter()
    }
    
    fun setupAdapter() {
        binding.exampleRecyclerView.adapter = ExampleAdapter()
    }
}

진짜 끝이다. 이제 실행된다.

 

이 글에 대한 코드는 아래에서 확인할 수 있다.

https://github.com/HamBP/recyclerview-test

 

HamBP/recyclerview-test

Contribute to HamBP/recyclerview-test development by creating an account on GitHub.

github.com

 

다음 글에서는 같은 코드를 기반으로 이벤트까지 바인딩시키는 방법을 알아본다.