안드로이드

[Kotlin/Android] RecyclerView (완) 여러 뷰홀더 더 만들어보기 - Custom 코드

란서 2022. 1. 11. 00:09

Custom코드 - 또 다른 데이터 클래스 뷰홀더 넣기. 

 RecyclerView (6)에서 Header ViewHolder 추가하여 리사이클러뷰에 header item이 포함된 목록을 보여줄 수 있게 하였다. 실제 데이터가 없는 header item은 단지 fix된 내용의 textView로만 구성되어 있었기 때문에 따로 데이터클래스를 만들어 Room Database와 연계해줄 필요가 없었기에 간단하게 추가할 수 있었다.

 

 그렇다면 실제 데이터가 존재하는 Data class OnStudy를 새로이 만든 뒤, 기존에 있던 SleepNight item리스트가 끝나면 그 다음 OnStudy Item 리스트를 보여주는 리사이클러뷰를 만들고자 한다.

1. Data class OnStudy 추가 과정.

 

Data class OnStudy

OnStudy 클래스는 유저가 공부한 시간을 기록하고 해당 공부시간의 퀄리티를 기록하는 데이터 클래스로 SleepNight와 매우 유사하다.
(이 커스텀 코드의 목적은 리사이클러뷰에 2가지 데이터 클래스 아이템 목록을 보여주기 위함이므로 유사한 클래스를 구현.)

 

따라서 OnStudy 데이터 클래스와 이에 맞는 Interface DAO, 그리고Database내에 새로운 타입의 Dao 프로퍼티를 추가하였다.

추가된 클래스 및 인터페이스
OnStudy Data Class
OnStudyDatabaseDao Interface
기존 데이터베이스 추상 클래스에 새로운 타입의 프로퍼티 추가.

 

Fragment 및 ViewModel 그리고 레이아웃 파일.

 

 

추가된 프래그먼트와 뷰모델

 

추가된 레이아웃 파일들
변경된 SleepTracker 레이아웃

 

변경된 네비게이션 파일

 

2.RecyclerView ViewHolder 추가 과정.

RecyclerView (6) 에서 header를 추가했던 과정과 마찬가지로

 

sealed class DataItem 

새로운 데이터에 맞는 Adapter와 ViewHolder class를 개조하기 위해서는 sealed class DataItem 내에 OnStudy 타입의 Class와 StudyHeader클래스를 만들어주자.

 

sealed class DataItem

 

 DiffCallback class - 의문점 (정리가 안되서 뭐가 의문인진 나만 이해할듯)

DataItem class 내에 새로운 클래스 OnStudyItem가 들어오면서 고유 id가 기존 SleepNightItem가 겹칠 수 가 있을텐데.. 

 

왜냐면 이제 Room Entitiy로써 다른 entity이고 각자 고유한 스키마의 고유한 id를 가지기 때문에 룸 데이터베이스 내에서는 구별될 순 있겠지만, 이제 여기서는 Long타입의 id일 뿐이니까 이게 Diffcallback의 메소드

 

override fun areItemsSame() 에서 id를 서로 비교하는 것으로 해당 아이템의 삭제, 추가가 걸러질 수 있느냐 이말이지.. 내가 아직 diffcallback 메소드가 어떻게 이루어지는지 몰라서 그런것 같기도 하고.. 

 

좀더 공부해봐야 할듯.

 

암튼 실행결과는 아직까진 큰 문제가 발생하진 않았는데,, 나중에 delete 까지 추가하면서 봐야 제대로 이게 수행하고 있는지 검사할 수 있을듯.

 

 

전역변수 ITEM_VIEW_TYPE_~ 정의

새로운 ViewHolder를 만들기 위해서는 타 뷰홀더들과 구분이 가능케하는 ViewType을 지정해 줘야 한다. 따라서 Adapter 클래스가 있는 파일 내에 전역 변수를 정의한다.

 

추가된 ViewType 전역변수

 

새로운 ViewHolder 클래스 생성

Adapter 클래스 내에 새로운 ViewHolder 클래스를 생성한다. 

 

 OnStudyViewHolder 클래스는 실제 데이터가 있는 아이템 뷰를 사용해야 한다. 따라서 연결되는 뷰 레이아웃과의 데이터 바인딩이 필요하다. binding 프로퍼티를 생성자로 받는 ViewHolder클래스를 만들고, 내부에 bind함수와 from함수를 구현하자.

OnStudyViewHolder class

 

 

 SleepNight의 헤더와 마찬가지로 TextView로만 구성된 뷰 레이아웃이다. 따라서 데이터 바인딩이 필요없고 View타입의  매개변수를 부모 클래스에 넘겨주면 되고, from함수만 구현하자.

StudyHeaderViewHolder class

 

Adapter class 개조.

 

  1. 우선 아이템(위치)에 따라 적절한 ViewType을 반환하는 메소드를 개조한다.
    getItemViewType 메서드


  2. onCreateViewHolder 메서드 내에 when(viewType) 내에 추가 케이스에 대한 코드를 구현한다.

    onCreateViewHolder 메서드


  3. onBindViewHolder 메서드 내에 when(holder) 내에 추가 케이스에 대한 코드를 구현한다.

    onBindViewHolder 메서드


  4. custom Submit() 메서드를 다시 구현한다. - 이 부분이 핵심

     기존에는 List<SleepNight>? 라는 하나의 매개변수만 받았지만, 이제 List<OnStudy>? 매개변수도 받아서 처리해야 한다. 

     만약 모든 매개변수값이 null 이라면 header1의 리스트와 header2의 리스트가 나란히 sumbit 될 것이다.  그렇지 않다면 매개변수로 들어오는 두 개의 List 중 적어도 하나는 null 아니라는 것으로 해당 리스트를 map함수를 이용하여 내용물을 DataItem클래스로 변경시켜 리스트화 한다.

    (만약 null이 있는 경우, listOf() 를 반환하도록 만든다.)

     리스트가 리사이클러뷰에 나타나는 순서는
    [Sleep Header] - [SleepNight List] - [Study Header] - [OnStudy List] 이다.

    따라서 다음과 같이 리스트를 만들어 submit 한다.

        fun addHeaderAndSubmitList(list:List<SleepNight>?, list2:List<OnStudy>?) {
            adapterScope.launch {
    
                val items = if(list==null && list2==null) {
                    listOf(DataItem.Header) + listOf(DataItem.HeaderStudy)
                } else {
                    listOf(DataItem.Header) + (list?.map{DataItem.SleepNightItem(it)} ?: listOf()) + listOf(DataItem.HeaderStudy) + (list2?.map{DataItem.OnStudyItem(it)} ?: listOf())
                }
                withContext(Dispatchers.Main) {
                    submitList(items)
                }
            }
        }​
     



ClickListener 구현

리사이클러뷰에 표시된 OnStudyDataItem ItemView를 클릭 했을 시 (OnStudyDetail Fragment 로 이동) 상호작용을 만들어 주기 위해 새로운 클릭 리쓰너를 만들고, Adapter 내에 해당 클릭리스너 타입의 프로퍼티를 새로 선언한다.

 

OnStudyClickListener 클래스

 

 이 후 프래그먼트 내에서 클릭 리스너에 구현하고자 하는 람다식을 넣은 클릭 리스너 클래스를 adapter 클래스의 인자로 넘겨준다.

 

In SleepTrackerFragment

 

 

 

 

3. BindingAdapter 추가.

 

 OnStudy 데이터 클래스가 새로 생기면서 여러 뷰모델과 레이아웃 파일이 생겼다. 따라서 OnStudy 데이터를 처리할 수 있는 View.확장함수를 새로 만들어 BindingApapter 어노테이션을 이용하여 dataBinding을 더욱 용이하게 만든다.


BindingAdapter 파일.
list_item_on_study.xml

 

 

4.Fragment에서 Apdater로 데이터 초기화 (SleepNight & OnStudy)

 

어떻게 해야 두 데이터를 한꺼번에 넘겨줄 수 있을까 고민 했던 파트. (adapter 클래스의 custom submit메소드는 이제 두 개의 데이터클래스 매개변수를 필요로 하기 때문)

 

그러다가 간단하게 생각.

 

 데이터베이스 내에 SleepNight 엔티티가 업데이트 될 때는 프래그먼트에서 관찰하고 있다가 데이터를 초기화시켜주는데, 이 때 OnStudyViewModel 을 참조하여 , OnStudy의 List 데이터를 꺼내서 같이 submit 로 넘겨주면 되겠다 싶음.

 

그 반대 상황도 마찬가지.

 

nights를 관찰하다가 업데이트 되면 OnStudyTrackerViewModel로 부터 studies의 value를 가져와 함께 submit
studies를 관찰하다가 업데이트 되면 SleepTrackerViewModel로 부터 nights의 value 가져와 함께 submit

 

근데 이렇게 해도되는건진 확실히 모르겠다.

 

 

 

결과

 

SleepTracker - OnStudyQuality - OnStudyDetail (Fragment)

 

 

문제점

 DiffCallBack (id)의 문제인지, 데이터를 넘기는 submit 과정에서의 문제인진 몰라도, item view가 추가될 때 OnStudy아이템 뷰가 살짝 살짝 불안하게 위치를 찾아가는 모습이 보인다. 

 

 

다음 코드 예정

Long 클릭리스너에 delete를 만들어서 item view를 삭제시킨다면 뭐가 문제인지 파악할 수 있을 듯.