DataBinding & Adapter
- Adapter와 DataBinding (with list_item)
리사이클러뷰에 보여질 views = list_item.xml을 dataBinding 하도록 재구성한다.list_item_sleep_night.xml
기존 root가 ConstraintLayout이었다면, layout 으로 바꾼 뒤, <data>와 <ConstraintLayout>으로 구분하여 재구성한다. -> 쉽게 alt+enter를 이용하여 바꿀 수 있다.
<?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/quality_image" .../> <TextView android:id="@+id/sleep_length" .../> <TextView android:id="@+id/quality_string" ... /> </androidx.constraintlayout.widget.ConstraintLayout> </layout>
- class Adapter 의 class ViewHolder 변경. - val binding 프로퍼티 사용 및 from()수정
기존에는 ViewHolder 클래스에서 뷰를 참조하기 위해 findViewById() 함수를 이용하여 뷰와 data를 bind하였다.
class ViewHolder private constructor(itemView: View) : RecyclerView.ViewHolder(itemView) { val sleepLength : TextView = itemView.findViewById(R.id.sleepLength) val sleepQuality : TextView = itemView.findViewById(R.id.sleepQuality) val sleepImage : ImageView = itemView.findViewById(R.id.sleepImage) fun bind(item: SleepNight) { val res = itemView.context.resources sleepLength.text = ~~~ sleepQuality.text = ~~~ sleepImage.setImageResource(~~) } }
binding을 이용한 ViewHolder class를 만들기 위해서는 companion object {} 내 from () 함수를 변경하면서 시작할 수도 있다. (순서 중요 않지만, from을 바꿔서 자연스럽게 바꾸기 가능)
기존 from() 함수
에서//기존 from함수 companion object { fun from(parent:ViewGroup) : ViewHolder{ val layoutInflater = LayoutInflater.from(parent.context) val view = layoutInfalter.inflate(R.id.list_item_sleep_night, parent, false) return ViewHolder(view) } }
companion object { fun from(parent: ViewGroup): ViewHolder { val layoutInflater = LayoutInflater.from(parent.context) val binding = ListItemSleepNightBinding.inflate(layoutInflater,parent,false) // val view = layoutInflater.inflate(R.layout.list_item_sleep_night, parent, false) return ViewHolder(binding) } }
으로 from함수를 binding 변수를 이용해 ViewHolder 클래스를 반환할 수 있도록 바꾸어 주었다.
하지만 아직 ViewHolder 클래스는 매개변수로 View 타입의 변수를 받고 있기 때문에, 이를 변경해주도록 하자.
기존 class에서는 View 타입의 매개변수를 받고, 이를 RecyclerView.ViewHolder의 인자로 넘겨주었지만//기존 class ViewHolder private constructor(itemView: View) //: RecyclerView.ViewHolder(itemView) //변경 class ViewHolder private constructor(val binding: ListItemSleepNightBinding) : RecyclerView.ViewHolder(binding.root) {...}
변경된 class에서는 ListItme~~Binding 타입의 프로퍼티를 만들어 해당 프로퍼티를 초기화 한뒤, binding.root를 RecyclerView.ViewHolder의 인자로 넘겨주고 있다.
(RecyclerView.ViewHolder에서 필요한 매개변수가 View가 필요하나봄?)
따라서 앞으로 ViewHolder class내부에서는 binding을 활용하여 코드 내에서 .xml의 코드를 수정 및 변경할 수 있다.
다음과 같이 기본 프로퍼티들을 모두 없애주고, binding변수를 통하여 직접 .xml view들을 참조하는 모습.class ViewHolder private constructor(val binding: ListItemSleepNightBinding) : RecyclerView.ViewHolder(binding.root) { //val sleepLength : TextView = itemView.findViewById(R.id.sleepLength) //val sleepQuality : TextView = itemView.findViewById(R.id.sleepQuality) //val sleepImage : ImageView = itemView.findViewById(R.id.sleepImage) fun bind(item: SleepNight) { val res = itemView.context.resources binding.sleepLength.text = ~~~ binding.sleepQuality.text = ~~~ binding.sleepImage.setImageResource(~~) } }
(굳이 프로퍼티를 생성하지 않고 Inline코드로 만들어주었다.) - lits_item_sleep_night.xml 코드의 데이터 바인딩 변수 sleep 을 이용하여 더 안전하고 간편한 코드 만들기.|
SleepNightAdatper가 있는 package 내에 BindingUtils 를 만든다.sleeptracker/BindingUtils
BindingUtils class 는 View 들의 확장함수로써 코드 내에서 다양한 타입의 뷰들을 다뤄 .xml 코드에서보다 좀 더 간편하고 가독성있게 뷰를 수정할 수 있게 만든다.
이렇게 코드 내에서 textView확장함수를 사용하여 adapter와 xml코드가 Binding을 하기 위해서
어노테이션 @BindingAdapter() 이 필요하다. 인자로 .xml에서 사용할 이름을 String 타입으로 넣어주면 .xml에서 "app:이름" 으로 사용 가능하다.
BindingUtils 코드를 구현한 뒤, Adapter class로 돌아가 직접적으로 data와 view를 연결시키는 bind()함수를 수정한다.class BindingUtils { @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 } ) } }
현재 bind() 함수는 binding을 사용하여 view를 참조하고 있지만, list_item_sleep_night.xml 코드의 데이터 바인딩 변수 sleep에 bind의 매개변수 item을 연결시켜주면 더 간편하게 binding을 이룰 수 있다.
fun bind(item: SleepNight) { binding.sleep = item binding.executePendingBindings() }
다음과 같이 binding.sleep= item을 초기화 하여준다.
binding.executePendingBindings()는 현재 binding되지 않은 보류된 view들(?)또한 빠르게 binding시켜주는 함수라 고 한다.(정확히 모름)
이 후, .xml 코드에서 (viewmodel 처리 하듯이) 각각 view들의 text 또는 image들을 sleep 변수를 참조하여 사용할 수 있다.
각각의 View 들이 BindingUtils 내에 구현된 확장함수들을 @BindingAdapter()의 도움으로 사용할 수 있게 되었다. (맞나?)<?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/quality_image" ... app:sleepImage="@{sleep}" /> <TextView android:id="@+id/sleep_length" ... app:sleepDurationFormatted="@{sleep}" /> <TextView android:id="@+id/quality_string" ... app:sleepQualityString="@{sleep}" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout>
정리
RecyclerView 아키텍쳐는 여러가지가 있다. 각자의 app상황에 따라 때로는 간편하게 구성할수 도 (그다지 크지 않은 데이터 목록일 때) 있으며, 때로는 복잡한 구조를 가질 수도 있다. (DataBinding과 large data list를 가질 때)
따라서 (1),(2),(3) 에 정리한 모든 아키텍쳐 구조들을 잘 습득해두고, 적절한 환경에 따라 리사이클러뷰를 구현하기 바란다.