스크롤뷰 없애고 리사이클러뷰로 레이아웃 고치기

상상코딩·2022년 3월 16일
0

안드로이드

목록 보기
12/21

스크롤뷰 없앤 이유? 문제점?

1. ScrollView

가장 기본적으로 쓰는 scrollview.. 이 안에 리사이클러뷰를 넣으면 리사이클러뷰 스크롤이 안되고 전체 스크롤 뷰가 움직이는 현상이 있었다.

그래서 쓰게된,,

2. NestedScrollView

이걸 쓰고 나면 이제 스크롤도 되고 리사이클러뷰 스크롤도 잘 됐다! 그러나,,,!!!! 가끔 스크롤을 하다보면 코드대로 잘 작동을 안할 때가 있었다.

디버깅을 해보니 뷰가 다시 보이는데, bind()함수가 타지를 않는다..?!

이유 : NestedScrollView가 뷰 자체가 스크롤 됐다고 판단하고 recyclerview가 스크롤됐다고 생각하지 않는 것.

해결방법:
리사이클러뷰에 android:nestedScrollingEnabled="false"속성 넣기



근데, 이 방법도 아주 큰 문제가 있었으니!!!

NestedScrollView 내부의 리사이클러뷰가 재활용 되지 않는다?!

NestedScrollView는 내부 리사이클러뷰의 아이템을 모두 그려놓는다. 따라서 재활용이 되지 않으며, 이는 리사이클러뷰의 장점을 크게 해치는 일이다!




그럼 해결 방법이 뭐야

바로바로.. 리사이클러뷰로 감싸버리기~!!(스크롤뷰 버리공)

1. 레이아웃을 다 쪼갠다.


여기서 위에 타일테스트라고 써있는 헤더타이틀 부분, 그리고 그 아래의 타일 리사이클러뷰 영역 총 2개의 영역으로 쪼갤 수 있다.
-> 이 기준으로 실제 xml을 분리해서 작성한다


2. 분리한 애들을 감쌀 레이아웃과 어댑터 만들기

1번에서전체 세로 리사이클러뷰에 하나씩 넣기 위해 레이아웃들을 분리했다.
그럼 이제 핵심인 전체 세로 리사이클러뷰를 만들어보자. (나는 이를 MainAdapter, main.xml이라 칭했다)

i) main.xml을 만들어 세로 리사이클러뷰 하나를 가지도록 한다.
ii) MainAdaper를 만든다.
iii) main.xml 레이아웃을 setContentView()하는 액티비티나 프래그먼트에서 리사이클러뷰 세팅(어댑터는 MainAdaper)을 한다.

3. MainAdapter 작성하기

MainAdaper에서 신경써야하는 부분,,

  • private val list = listOf<Unit>(Unit, Unit)
    나는 레이아웃이 픽스값인 2개이므로 Unit 리스트를 2개짜리로 초기화 했다.
    (레이아웃의 개수가 서버의 값에 따라 다르다거나 할 경우 동적으로 변경하면 되겠다!)
  • 서로의 뷰에 직접 접근 불가
    이제는 레이아웃들이 분리되어있다. 이 예제에서 헤더타이틀타일영역이 원래는 한 레이아웃에 있었기 때문에, binding.~~ 의 형태로 서로의 뷰를 바로 접근 할 수 있었으나, 이제는 MainAdapter의 어떤 변수나 함수를 사용하여 접근하는 형태여야 한다.
    ( 예를들어, 헤더타이틀영역에 확인 버튼이 있고, 이 버튼을 누를때, 타일영역의 뷰의 애니메이션을 멈추려고 한다. 그럼 MainAdapter의 변수인 타일영역어댑터(tileAdapter)의 stopAnimation()함수를 호출할 수 있다. )

작성한 코드는 아래와 같습니당

class MainAdapter : RecyclerView.Adapter<MainCommonViewHolder>() {
     private val list = listOf<Unit>(Unit, Unit)
     val tileAdapter by lazy { TileAdapter() }

     var startEditModeEventInAdapter: (() -> Unit)? = null

     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainCommonViewHolder {
         val inflater = LayoutInflater.from(parent.context)

         return if (viewType == TYPE_TITLE)
             TitleViewHolder(ActivityMainTitleBinding.inflate(inflater, parent, false))
         else
             TileViewHolder(ActivityMainRvTileBinding.inflate(inflater, parent, false))
     }

     override fun onBindViewHolder(holder: MainCommonViewHolder, position: Int) {
         holder.bind()
     }

     override fun getItemCount(): Int = list.size


     override fun getItemViewType(position: Int): Int {
         return if (position == 0) TYPE_TITLE
         else TYPE_TILE
     }
}

그럼 이 예제에서는 실제로 3개의 xml이 만들어지는 것.

  • main.xml : 그냥 전체를 통으로 감싸기 위한 세로 리사이클러뷰 하나만을 가지는 xml
  • header.xml : 타일테스트라고 써있는 헤더타이틀 부분
  • tile_rv.xml : 타일 리사이클러뷰 영역




내가 올린 쏘스~
https://github.com/leehayeong/custom_tile_list_practice/pull/1/commits/fad6ebf0174ba406b97ca23405a136d3bf90baf0





+) 추가사항
2021년 10월쯤, ConcatAdapter가 등장!!!
얘는 어댑터가 다양하게 존재할때 사용하면 너무 좋을 것 같다. 위에 만들어둔 리사이클러뷰로 감싸는 처리랑 비슷한 느낌이다.




예를 들어, 내가 원하는 뷰가 아래와 같은 형태를 띤다고 하자.

[A: 헤더 레이아웃 영역, B: 카드 리사이클러뷰 영역, C: 사진 리사이클러뷰 영역, D: 일기 리사이클러뷰 영역]

  1. RecyclerView로 처리
    이 경우, 세로 큰 리사이클러뷰 내에 4개의 뷰타입을 가지고 해당 네개의 영역을 나누어 그려줘야한다.
  2. ConcatAdapter로 처리
    뷰타입 없이 그냥 ConcatAdapter(여기에 4개의 어댑터 나열)이런식으로 구현한다.
    그러나 여기서는 A-헤더 레이아웃 과 같은 형태의 뷰만 있는 레이아웃에도 어댑터를 만들어줘야하는 단점이 있다.
profile
히히낙낙

0개의 댓글