DiffUtil 및 RecyclerView를 사용한 데이터 바인딩
DiffUtil사용하는 이유
-> 데이터가 바뀔때마다 notifyDataSetChanged()를 사용해줘서 전체 리스트를 변경하는 번거로움이 있었다.
DiffUtil 이라는 클래스는 두 목록간의 차이점을 찾고 업데이트 되어야 할 목록을 반환해주며 최소한의 업데이트 수를 계산함 !
그래서 해결책으로 DiffUtil을 사용한다고 한다.
1. DiffUtillCallback 클래스 구현하기
DiffUtil.ItemCallback을 상속하는 클래스를 구현해준다. <>안에 반환해줄 자료형을 써주기
-
- areItemsTheSame
- 전달된 두 항목 oldItem, newItem이 동일한지 체크해서(id비교) Boolean값 리턴
-
- areContentsTheSame
- 내부에서 동일한 데이터가 있는지 확인, 모든 필드 검사
SleepNightDiffCallback.kt
package com.example.android.trackmysleepquality.sleeptracker
import androidx.recyclerview.widget.DiffUtil
import com.example.android.trackmysleepquality.database.SleepNight
class SleepNightDiffCallback : DiffUtil.ItemCallback<SleepNight>() {
override fun areItemsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
return oldItem.nightId == newItem.nightId
}
override fun areContentsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
return oldItem == newItem
}
}
2. RecyclerView Adapter 바꿔주기
RecyclerView.Adapter
SleepNightAdapter
class SleepNightAdapter: ListAdapter<SleepNight, SleepNightAdapter.ViewHolder>(SleepNightDiffCallback()){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder.from(parent)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position)
holder.bind(item)
}
}
3. Item데이터바인딩 해주기
item_sleep_tracker에서 데이터바인딩을 사용하기 위해 layout과 sleepNight태그를 추가해줘야한다.
item_sleep_tracker.xml
<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="sleep"
type="com.example.android.trackmysleepquality.database.SleepNight" />
</data>
...
</layout>
그리고 viewHolder에서 view로 inflate했던 부분을 데이터바인딩으로 inflate하도록 바꿔준다.
companion object {
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ItemSleepTrackerBinding.inflate(layoutInflater,parent,false)
return ViewHolder(binding)
}}
viewHolder의 인수로 binding 을 넘겼기 때문에, constructor의 매개변수도 ItemSleepTrackerBinding으로 바꿔줘야한다.
class ViewHolder private constructor(val binding: ItemSleepTrackerBinding) : RecyclerView.ViewHolder(binding.root)
그리고 데이터바인딩을 사용했기 때문에ViewHolder에서 view.findViewById로 찾아줬던 변수들을 삭제해줘도 된다.
마지막으로 선언해줬던 변수들을 삭제해준 뒤, bind 함수 안에 xml에서 바인딩 변수로 사용한 sleep 에다가 item을 할당해주는 코드를 써준다.
fun bind(item: SleepNight) {
binding.sleep = item
binding.executePendingBindings()
}
4. 바인딩 어댑터 사용하기
저번에 Transformation을 사용해서 바인딩하는 데이터를 바꿔줬었는데, 이번엔 바인딩 어댑터를 사용한다. 바꿀 내용이 간단할때는 Transformations을, 보다 복잡한 내용은 바인딩 어댑터를 사용하는것이 좋다.
바인딩 어댑터를 선언할 BindingUtils.kt를 만들어주고 그 안에 viewHolder에서 TextView나 ImageView의 값을 설정해줄때 썼던 내용 그대로 써주면 된다. ㅎㅎ
BindingUtils.kt
package com.example.android.trackmysleepquality.sleeptracker
import android.widget.ImageView
import android.widget.TextView
import androidx.databinding.BindingAdapter
import com.example.android.trackmysleepquality.R
import com.example.android.trackmysleepquality.convertDurationToFormatted
import com.example.android.trackmysleepquality.convertNumericQualityToString
import com.example.android.trackmysleepquality.database.SleepNight
@BindingAdapter("sleepDurationFormatted")
fun TextView.setSleepDurationFormatted(item: SleepNight){
text = convertDurationToFormatted(item.startTimeMilli,item.endTimeMilli,context.resources)
}
@BindingAdapter("sleepQualityString")
fun TextView.setSleepQualityString(item: SleepNight) {
text = convertNumericQualityToString(item.sleepQuality, context.resources)
}
@BindingAdapter("sleepImage")
fun ImageView.setSleepImage(item: SleepNight) {
setImageResource(when (item.sleepQuality) {
0 -> R.drawable.ic_sleep_0
1 -> R.drawable.ic_sleep_1
2 -> R.drawable.ic_sleep_2
3 -> R.drawable.ic_sleep_3
4 -> R.drawable.ic_sleep_4
5 -> R.drawable.ic_sleep_5
else -> R.drawable.ic_sleep_active
})
}
바인딩 어댑터 (“이름”)을 사용해서, xml안의 app:이름 ~ 이렇게 접근할 수 있다.
item_sleep_tracker.xml
<?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="sleep"
type="com.example.android.trackmysleepquality.database.SleepNight" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView android:id="@+id/img_sleep_quality"
android:layout_width="@dimen/icon_size"
android:layout_height="60dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
app:sleepImage="@{sleep}"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@drawable/ic_sleep_5" />
<TextView android:id="@+id/tv_sleep_length"
android:layout_width="0dp"
android:layout_height="20dp"
app:sleepDurationFormatted="@{sleep}"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/img_sleep_quality"
app:layout_constraintTop_toTopOf="@+id/img_sleep_quality"
tools:text="Wednesday" />
<TextView android:id="@+id/tv_sleep_quality"
android:layout_width="0dp"
android:layout_height="20dp"
android:layout_marginTop="8dp"
app:sleepQualityString="@{sleep}"
app:layout_constraintEnd_toEndOf="@+id/tv_sleep_length"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="@+id/tv_sleep_length"
app:layout_constraintTop_toBottomOf="@+id/tv_sleep_length"
tools:text="Excellent!!!" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Fragment에서 adapter.submitList()를 사용한 코드로 바꿔주면 끝 !
SleepTrackerFragment.kt
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
it?.let{
adapter.submitList(it)
}
})
참고
https://developer.android.com/codelabs/kotlin-android-training-diffutil-databinding/#0
Comments