profile image

L o a d i n g . . .

반응형

안드로이드 코틀린 중첩 리싸이클러뷰, expendable 3뎁스 리싸이클러뷰 구현

Android Kotlin recyclerview in recyclerview, 3depth expandable recyclerview

 

일반 리싸이클러뷰는 많이 구현해보았고, 2depth까지 구현된 리싸이클러뷰는 라이브러리가 몇개 있다. 그런데 이번에 해봐야 할 것은 3depth까지 펼쳐져야하는 리싸이클러뷰를 구현해야 하여 많은 고민 끝에 중처 리싸이클러뷰로 만들기로 했다.

일반 리싸이클러뷰 처럼 리스트에 타입을 나눠서 1,2,3 레벨 모두 넘겨서 작업을 하다보면 리싸이클러뷰를 펼치기를 해서 화면밖으로 스크롤이 생길 때 position 및 size가 꼬여서 다시 아이템들이 재활용되는 상황을 볼 수 있다.

나는 조금 다른 방법으로 구현을 해보았으며 이게 맞다고 하기에는 퀄리티가 많이 부족하여 참고자료정도로 사용하며 좋을 듯 싶다

 

일단 제일 밖의 리싸이클러뷰는 기존과 같에 리싸이클러뷰 하나만 구현하면 된다.

<androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerview"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"/>

특별한 것이 단 하나도 없도. 코드로 layoutManager을 정의하기 귀찮아서 xml에 바로 적용을 시켜버렸다.

list.add("LiverPool")
list.add("Manchester")
list.add("ManCity")
list.add("Other")
recyclerview.adapter = HeaderAdapter(list, this)

adpater에 데이터를 넘겨주는 것도 샘플이므로 하드코딩으로 직접 리스트에 데이터를 담아 보냈다. list와 context만 보내도록 했다.

리싸이클러뷰의 꽃은 역시 adapter가 아니겠는가. adapter를 보도록 하자

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HeaderAdapter.HeaderViewHolder {
        val view: View = LayoutInflater.from(parent.context).inflate(R.layout.list_item, parent, false)
        viewHolderList.add(HeaderViewHolder(view))
        return HeaderViewHolder(view)
    }

override fun getItemCount(): Int {
        return list.size
    }

override fun onBindViewHolder(holderHeader: HeaderViewHolder, position: Int) {
        list[position].let {
        	holderHeader.bind(it, viewHolderList)
        }

        holderHeader.text.setOnClickListener {
            holderHeader.clickEvent()
        }
    }

기본으로 셋팅해 줄 데이터들은 셋팅을 해주되, 하나 다른 점은 onCreateViewHolder에서 볼 수 있다.

inflate만 하고 return 하는 것이 아니라, 해당 viewHolder들을 담아줄 list를 전역변수로 하나 만들어 viewHolder가 만들어 질때마다 담아지게 하였다.

그리고 onBindViewHolder에서 viewholder로 데이터를 보내 레이아웃에 그려주는 것과 클릭 이벤트를 처리하도록 하였다.

<TextView
        android:id="@+id/header"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@android:color/holo_red_dark"
        android:textSize="20dp"
        tools:text="Header Text"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"/>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/sub"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        app:layout_constraintTop_toBottomOf="@+id/header"
        app:layout_constraintStart_toStartOf="parent"/>

recyclerview에 들어갈 viewholder의 layout에는 중첩 recyclerview이기 때문에 또 한번 recyclerview가 들어가야 하며, 위 recyclerview에는 라이브러리가 들어갈 자리이다. 그래서 3depth여도 recyclerview는 하나만 만들어도 된다.

이것도 바깥의 recyclerview와 마찬가지로 layoutManger은 xml상에서 정의 하였다.

inner class HeaderViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

        var text : TextView = itemView.findViewById(R.id.header)
        var subRecycler : RecyclerView = itemView.findViewById(R.id.sub)
        var companies = ArrayList<Comapny>()
        var headerViewHolder = ArrayList<HeaderViewHolder>()
        var isClose = false

        fun bind(header: String, viewHolderList: ArrayList<HeaderViewHolder>) {
            text.text = header
            this.headerViewHolder = viewHolderList
        }

        fun clickEvent() {
            if (!isClose) {
                for (view in headerViewHolder) {
                    view.companies.clear()
                    view.isClose = false
                    view.subRecycler.adapter = ProductAdapter(view.companies)
                }
                companies.add(getSampleDataZip1())
                companies.add(getSampleDataZip2())
                isClose = true
            } else {
                companies.clear()
                isClose = false
            }
            subRecycler.adapter = ProductAdapter(companies)
            for (view in headerViewHolder) {
                notifyItemChanged(view.adapterPosition)
            }
        }
    }

viewholder는 inner클래스로 만들었다. 

bind에서는 특별한 기능 없이 받은 데이터(text)를 레이아웃에 그려주며, 위에서 viewHolder를 담은 list를 해당 bind에서 받도록 하였다.

clickEvent에서는 클릭하였을 때, 클릭된 텍스트 아래에 정의한 recyclerview에 setAdapter를 통해 붙여주고 있으며, 열릴때는 add를 시켜주고, 다시 클릭하여 닫기를 할 때는 clear을 통해 다시 닫혀지게끔 구현하였다.

for문에서는 하나의 아이템이 열린 상태에서 다른 아이템을 클릭하였을 때 이미 열려진 아이템을 닫아주고 클릭한 아이템만 다시 펼쳐지게끔 하기 위해 모든 viewHolder들에 add된 데이터들을 모두 clear을 하도록 하였다.

마지막에 아답터에 셋팅을 해준 후 다시 for을 통해 notifyItemChanged를 한 이유는 부드럽게 애니메이션이 적용되게 하기 위해서였다. 

notifyItemChanged를 하지 않아도 펼치지 접기 기능은 정상적으로 작동하되, 팍! 팍! 작동을 한다. 

 

투뎁스의 데이터들은 위 adpater에서 하드코딩으로 만들어서 적용하였으며, 혹시 궁금해할까봐 대충 이렇게 만들었다고 올려놓겠다

fun getSampleDataZip1() : Comapny {
        var googleProduct = ArrayList<Product>()
        googleProduct.add(Product("google Ad"))
        googleProduct.add(Product("google Drive"))
        googleProduct.add(Product("google Service"))
        googleProduct.add(Product("google Photo"))
        var companyGoogle = Comapny("Google", googleProduct)

        return companyGoogle
    }

fun getSampleDataZip2() : Comapny {
        var microSoftProduct = ArrayList<Product>()
        microSoftProduct.add(Product("Microsoft Window"))
        microSoftProduct.add(Product("Microsoft Game"))
        microSoftProduct.add(Product("Microsoft Image"))
        microSoftProduct.add(Product("Microsoft Service"))
        microSoftProduct.add(Product("Microsoft Other"))
        var companyMicroSoft = Comapny("MicroSoft", microSoftProduct)

        return companyMicroSoft
    }

 

2depth의 라이브러리는 expandablerecyclerview를 사용하였으며 해당 라이브러리의 깃허브는 다음과 같다

github.com/thoughtbot/expandable-recycler-view

 

thoughtbot/expandable-recycler-view

Custom Android RecyclerViewAdapters that collapse and expand - thoughtbot/expandable-recycler-view

github.com

implementation 'com.thoughtbot:expandablerecyclerview:1.4'

업데이트 상태를 보니 몇년전부터 전혀 업데이트가 이루어지지 않은것으로 보이나 androidx recyclerview로 구현을 하였는데, 잘 작동하는걸 보니 큰 문제는 없어 보인다. 

해당 라이브러리의 사용법은 해당 깃 허브에 자세하게 나와있으며, 유튜브에 검색을 해보아도 예제동영상들을 찾아볼 수 있다.

 

내가 만든 코드는 아직 디테일함이 부족하며, 애니메이션 효과나 클릭 이벤트의 수정이 조금 필요하다. 그래서 참고자료로 정도만 사용하면 된다.

 

 

위 코드를 빌드해서 나온 결과물이여

1depth가 빨간색 

2depth가 하늘색

3depth가 보라색

으로 눈으로 구분하기 쉽게해놓았다. 일단 2depth부터 라이브러리를 사용하였고, 데이터도 adpater에서 셋팅해주기 때문에 가장 위에 있는 recyclerview의 position 및 size는 꼬일일이 없다.

하나 문제점이라면 라이브러리를 사용하였기에, 커스텀을 하고 싶을 경우 약간 골치가 아프다.

반응형
복사했습니다!