데이터바인딩 Recyclerview

DWL5·2020년 11월 2일
0
post-thumbnail

영화검색 어플리케이션의 Recyclerview는 Movie라는 데이터를 받아 화면에 그립니다.
이번 포스팅에서는 데이터 바인딩을 사용하여 Recyclerview를 구현하는 방법을 알아보겠습니다.

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>

        <variable
            name="movie"
            type="com.world.tree.architecturestudy.model.Movie.Item" />

    </data>
    
    ...
  </layout>

Recyclerview ViewHolder에 들어어가는 레이아웃 입니다. layout 태그로 감싸져있고, data태그 안에는 movie라는 variable이 정의되어 있습니다.

RecyclerView의 Adapter에서는 binding 객체를 받는 ViewHolder를 정의해 주고, onCreateViewHolder에서 binding객체를 전달 하여 ViewHolder 객체를 만듭니다.

onBindViewHolder에서는 각 포지션에 맞는 Movie 데이터를 가져와서 ViewHolder와 연결시켜 줍니다.

이 때 layout파일 내에 정의된 변수인 movie에 데이터를 전달 해주면 layout파일 내에서 해당 데이터를 사용하여 데이터 바인딩을 할 수 있습니다.

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MovieViewHolder {
        val binding = DataBindingUtil.inflate<ItemMovieBinding>(LayoutInflater.from(parent.context),
            R.layout.item_movie, parent, false)
        return MovieViewHolder(binding)
    }
    
    inner class MovieViewHolder(private val binding: ItemMovieBinding) : RecyclerView.ViewHolder(binding.root) {
        fun bind(data: Movie.Item) {
            binding.movie = data
            if (::onItemClickListener.isInitialized) binding.onItemClickListener =
                onItemClickListener
            binding.executePendingBindings()
        }
    }
    
    override fun onBindViewHolder(holder: MovieViewHolder, position: Int) {
        holder.bind(movies[position])
    }

layout으로 데이터를 전달 했다면 바인딩을 어떻게 해야 할까요?
아래와 같이 @{}구문을 사용하여 바인딩 합니다. 기존의 코틀린이나 자바파일에서 setText를 해줄 필요 없이 layout파일 내에서 데이터 바인딩이 가능해 졌습니다.

<TextView
                android:id="@+id/txtTitle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:maxLines="1"
                android:text="@{movie.title}"
                app:layout_constraintBottom_toTopOf="@+id/txtDirector"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent"/>

그러면 네트워크 통신을 통해 가져오는 데이터인 이미지는 어떻게 바인딩을 할까요. 데이터 바인딩 어댑터를 사용하여 커스텀 로직을 제공할 수 있습니다.

@BindingAdapter("loadImage")
fun loadImage(imageView: ImageView, url:String) {
    Glide.with(imageView).load(url)
        .centerCrop()
        .into(imageView)
}

이 프로젝트에서는 Glide 라이브러리를 사용했는데요. 위와 같이 정의된 함수를 레이아웃 파일에서 ImageView에서 사용할 수 있습니다. app:loadImage에 url를 전달해 주면 Glide라이브러리를 통해 파일을 받아 ImageView에 셋팅해 줍니다.

<ImageView
            android:id="@+id/imgPoster"
            android:layout_width="100dp"
            android:layout_height="100dp"
            app:layout_constraintBottom_toBottomOf="@id/layoutMovieInfo"
            app:layout_constraintEnd_toStartOf="@id/layoutMovieInfo"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:loadImage="@{movie.image}"/>

지금 앱은 Search 액션을 할 때마다 새로운 리스트들을 API를 통해 받아옵니다.
그에 따라 새로운 데이터들을 RecyclerView에 셋팅해 주어야 합니다.
지금 까지는 검색 할 때마다 MovieAdapter의 addData(data: List<Movie.Item>)함수를 호출하였습니다.

그러나 지금까지 알아본 데이터바인딩을 통해 리스트를 레이아웃 내에서 바인딩하려면 어떻게 해야할까요. RecyclerView에 새로운 데이터를 넣는 작업은 어댑터에 새 데이터를 추가해주고, notifyDataChanged()까지 호출해야 하므로 BindingAdapter를 통해 커스텀 된 로직을 추가해 줍니다.

그리고 변화된 데이터를 감지 할 수 있는 요소인 Observable이나 LiveData를 사용합니다.

@BindingAdapter("bindData")
fun bindData(recyclerView: RecyclerView, data : List<Movie.Item>) {
    val adapter = recyclerView.adapter as? MovieAdapter
    adapter?.let {
        it.clearData()
        it.addData(data)
    }

아래와 같은 요소로 데이터를 감싸주고, 이를 데이터 바인딩하게 되면 자동적으로 변화를 감지하여 app:bindData가 수행됩니다.

val movieList = ObservableArrayList<Movie.Item>()
<androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            app:bindData="@{viewModel.movieList}"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
            app:layout_constraintTop_toBottomOf="@id/etUrl"
            app:layout_constraintBottom_toBottomOf="parent"/>

LiveData와 Observable에 대해서는 다음 포스팅에서 좀 더 알아보겠습니다.

profile
안드로이드 개발자

0개의 댓글