본문 바로가기
안드로이드 코틀린

[Kotlin][Android] DataBinding + RecyclerView 함께 사용해보기

by teamnova 2022. 11. 18.

안녕하세요.

오늘은 안드로이드에서 많이 사용하는 리사이클러뷰를 데이터바인딩 라이브러리와 함께 사용하는 방법에 대해 알아보겠습니다. 

(이 글은 리사이클러뷰를 사용할 줄 안다는 전제하에 작성하였습니다.)

 

더보기

* Data Binding Library 이란?

데이터 바인딩은 안드로이드에서 제공하는 라이브러리로 

액티비티 코드 내에서 UI 컴포넌트에 데이터를 붙이는 것이 아니라

레이아웃에서 UI 컴포넌트와 데이터를 연결시켜놓을 수 있습니다. 

  • 기존 연결 방법
findViewById<TextView>(R.id.sample_text).apply {
    text = viewModel.userName
}

 

  • 데이터 바인딩
<TextView
    android:text="@{viewmodel.userName}" />

 

 

빈 프로젝트를 새로 생성한 후 데이터바인딩을 사용하기 위해

build.gradle(module) 파일에 아래 코드를 입력해줍니다.

buildFeatures{
    dataBinding true
}

 같은 파일에서 plugins{} 안에 아래 코드가 있는지 확인하고 없다면 추가해줍니다.

id 'kotlin-kapt'

 

뉴스앱을 만든다고 가정하고 LargeNews 라는 클래스를 생성해보겠습니다. 

 

리사이클러뷰의 아이템에 사용할 LargeNews 라는 이름의 data class를 추가해줍니다. 

data class LargeNews(
    val title: String,
    val image: Drawable?
)

 

다음으로 액티비티 또는 프래그먼트에 리사이클러뷰를 추가해보겠습니다.

  1. MainActivity.kt 생성
  2. activity_main.xml 생성 후 리사이클러뷰 추가
  3. activity_main.xml 의 최상단의 <?xml…?> 부근 왼쪽 가장자리를 클릭하면 전구가 표시되고 (또는 Alt+Enter 누름) "convert to data binding layout"을 클릭합니다. (직접 레이아웃의 가장 바깥을 <layout></layout> 으로 감싸도 됩니다.)

완성된 레이아웃은 아래와 같습니다.

<?xml version="1.0" encoding="utf-8"?>
<layout 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">

    <data>

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/rvNews"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

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

 

 

다음으로 리사이클러뷰 아이템 레이아웃을 만들어보겠습니다. 

데이터 바인딩을 위해  <data> 태그를 추가해줍니다.

<data> 태그 내의 타입은 pakage_name.class_name 입니다.

 

<data> 태그를 추가하면 아래와 같이 레이아웃 내에서 변수를 사용할 수 있습니다. 

//Backgroud to ImageView
android:background="@{largeNews.image}"
or
//Text to TextView
android:text="@{largeNews.title}"

 

아이템의 최종 레이아웃은 아래와 같습니다. 

<?xml version="1.0" encoding="utf-8"?>
<layout 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">

<!-- 데이터 바인딩에 사용할 데이터 선언 -->
    <data>

        <variable
            name="largeNews"
            type="com.canonal.news.model.LargeNews" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="8dp"
        android:background="@color/gray"
        android:padding="12dp">

        <ImageView
            android:id="@+id/ivLargeNews"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@{largeNews.image}"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:background="@drawable/android_icon" />

        <TextView
            android:id="@+id/tvTitleLargeNews"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:text="@{largeNews.title}"
            android:textColor="@color/black"
            android:textSize="18sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/ivLargeNews"
            tools:text="Large News Title" />
        
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

 

레이아웃을 만들었으니 adapter와 viewholder를 만들어보겠습니다. 

 

뷰홀더를 어댑터 내부에서 만들 수 있지만 분리해서 만들어보겠습니다. 

RecyclerView.Viewholder 를 상속받는 LargeNewsViewHolder 클래스를 만들어보겠습니다. 

데이터바인딩을 사용하기 위해 ItemLargeNewsBinding를 매개변수로 받겠습니다. 

그리고 bind 함수를 만들어 LargeNews를 매개변수로 얻습니다.

이 함수는 객체를 레이아웃에 바인딩합니다. (= 데이터와 레이아웃을 연결시켜줍니다.)

 

class LargeNewsViewHolder(
    private val binding: ItemLargeNewsBinding
) : RecyclerView.ViewHolder(binding.root) {
    fun bind(largeNews: LargeNews) {
        binding.largeNews = largeNews
    }
}

 

어댑터 클래스는 반드시 RecyclerView.Adapter<your_view_holder_class_name> 클래스를 상속받아야합니다. 

그리고 아래 3개의 함수를 ovveride 해줍니다. 

  • onCreateViewHolder: 새 아이템을 만듭니다. 
  • onBindViewHolder: 연결된 위치(position)에 데이터를 연결시켜줍니다.
  • getItemCount: 아이템리스트의 사이즈를 알려줍니다. 

 

어댑터의 최종 코드입니다. 

package com.canonal.news.adapter

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.canonal.news.databinding.ItemLargeNewsBinding
import com.canonal.news.model.LargeNews

class NewsAdapter(
    private val largeNewsList: List<LargeNews>
) : RecyclerView.Adapter<LargeNewsViewHolder>() {

    private lateinit var binding: ItemLargeNewsBinding

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LargeNewsViewHolder {
        binding = ItemLargeNewsBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return LargeNewsViewHolder(binding)
    }

    override fun onBindViewHolder(holder: LargeNewsViewHolder, position: Int) {
        val largeNews = largeNewsList[position]
        holder.bind(largeNews)
    }

    override fun getItemCount(): Int = largeNewsList.size

}

 

마지막으로 메인 액티비티에서 getDummyData 함수로 데이터를 가져옵니다. 

그리고 어댑터 객체를 만들고 

리사이클러뷰에 이 어댑터 객체를 연결해줍니다. 

또한 리사이클러뷰에 레이아웃 매니저를 만들어줍니다. 

package com.canonal.news

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.LinearLayoutManager
import com.canonal.news.adapter.NewsAdapter
import com.canonal.news.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)

        val recyclerViewNews = binding.rvNews
        val largeNews = DummyData.getDummyData(this)
        val newsAdapter = NewsAdapter(largeNews)

        recyclerViewNews.adapter = newsAdapter
        recyclerViewNews.layoutManager = LinearLayoutManager(this)
        recyclerViewNews.setHasFixedSize(true)
    }
}