Closed cheewr85 closed 2 years ago
앞서 본 내용에서는 RecyclerView에 구조 그리고 DiffUtil 사용 Data Binding 적용 등 RecyclerView를 구현하고 나타내는데 포커스를 맞춤
근데 단순히 List만 보여주는 것이 아닌 해당 item을 클릭 했을 때 다른 화면으로 넘어가던가 혹은 특정 이벤트를 처리할 것임
이때 버튼을 눌러서 setOnClickListener를 하듯이 할 순 없기 때문에 이때 Data binding의 이점과 콜백을 활용함
먼저 아이템에 대한 클릭 이벤트 처리를 위해서 아이템 값을 넘겨받고 onClick하는 클래스를 adapter에 추가함
// item click listener에 대한 클래스, 콜백을 인자로 넣음
class SleepNightListener(val clickListener: (sleepId: Long) -> Unit) {
// 보여지는 list item을 클릭했을 때 onClick을 호출함, SleepNight의 타입 night를 넘김, 콜백으로 night의 id값을 넘김
fun onClick(night: SleepNight) = clickListener(night.nightId)
}
onClick
처리를 함<data>
<variable
name="sleep"
type="com.example.android.trackmysleepquality.database.SleepNight" />
<variable
name="clickListener"
type="com.example.android.trackmysleepquality.sleeptracker.SleepNightListener" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{() -> clickListener.onClick(sleep)}">
이렇게 한다면 해당 item_view를 클릭했을 때 앞서 정의한 클릭리스너 클래스의 onClick이 작용하고 sleep data를 통해서 해당 item에 대한 값이 넘어가게 됨
그리고 위의 사항들을 적용하기 위해서 adapter에도 역시 수정을 해줘야함, 먼저 adapter 클래스에 clickListener 인자를 생성자에 추가하고 ViewHolder를 통해 이 item_view를 만들기 때문에 이와 관련해서 인자로 clickListener를 추가하고 넘겨줌
class SleepNightAdapter(val clickListener: SleepNightListener) : ListAdapter<SleepNight, SleepNightAdapter.ViewHolder>(SleepNightDiffCallback()) {
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
// data에서 position의 값을 가져왔으나 ListAdapter 및 DiffUtil 사용해서 item만을 쓰면 됨
val item = getItem(position)
// viewHolder inner class에 정의한 함수 활용해서 처리함
holder.bind(getItem(position)!!,clickListener)
}
....
// image, textview가 있는 해당 itemView에 대한 ViewHolder를 정의함
class ViewHolder private constructor(val binding: ListItemSleepNightBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(
item: SleepNight,
clickListener: SleepNightListener,
) {
// sleep에 item을 할당함(db에서 불러온 값)
binding.sleep = item
// 최적화를 위해 호출
binding.executePendingBindings()
// clicklistener를 할당해줌
binding.clickListener = clickListener
}
// 앞서 만든 Adapter 생성
val adapter = SleepNightAdapter(SleepNightListener { nightId ->
// Toast.makeText(context, "${nightId}", Toast.LENGTH_LONG).show()
// viewModel에서 클릭 이벤트 리스너 등록함 리스너에서 받은 id를 넘김
sleepTrackerViewModel.onSleepNightClicked(nightId)
})
이제 위의 작업을 통해서 RecyclerView에 item을 클릭하게 된다면 해당 item의 id가 클릭 리스너로 넘어가서 처리됨
즉, 클릭시 상호작용은 활성화 된 것임, 하지만 여기서 그치지 않고 다른 화면으로 넘어가게끔 설계했기 때문에 이와 관련해서 navigation에 설정한 값으로써 설정하게 ViewModel
에서 LiveData
를 정의함
// Detail에 가기 위해 쓴 LiveData 변수
private val _navigateToSleepDetail = MutableLiveData<Long>()
val navigateToSleepDetail
get() = _navigateToSleepDetail
// id를 넘겨 Detail로 넘아가는 클릭 핸들러 함수
fun onSleepNightClicked(id: Long) {
_navigateToSleepDetail.value = id
}
// navigating이 끝나면 value를 초기화하는 함수
fun onSleepDetailNavigated() {
_navigateToSleepDetail.value = null
}
이제 위의 값을 가지고 Detail 화면으로 넘어가는 것에 대해서 값을 id를 넘겨서 처리해서 넘어가게 할 수 있음
최종적으로 이를 Fragment에서 adapter 인스턴스 생성한 부분에 위에서 정의한 ViewModel의 메소드를 추가하고 navigation 처리를 앞서 배운대로 처리함
// 앞서 만든 Adapter 생성
val adapter = SleepNightAdapter(SleepNightListener { nightId ->
// Toast.makeText(context, "${nightId}", Toast.LENGTH_LONG).show()
// viewModel에서 클릭 이벤트 리스너 등록함 리스너에서 받은 id를 넘김
sleepTrackerViewModel.onSleepNightClicked(nightId)
})
....
// 아이템이 클릭될 때 navigating을 위해 variable의 상태를 Observer하는 것을 추가함
sleepTrackerViewModel.navigateToSleepDetail.observe(viewLifecycleOwner, Observer { night ->
night?.let {
this.findNavController().navigate(SleepTrackerFragmentDirections.actionSleepTrackerFragmentToSleepDetailFragment(night))
sleepTrackerViewModel.onSleepDetailNavigated()
}
})
null
처리에 대한 ?
를 binding adapter에 추가해주면 됨
[질문]
RecyclerView에서 ClickHandler를 위해 Listener 처리와 관련해서 정리