영화검색 어플리케이션의 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에 대해서는 다음 포스팅에서 좀 더 알아보겠습니다.