[Android] NestedScrollView에 대해 알아보자!

김병수·2021년 8월 5일
20
post-thumbnail

1. NestedScrollView를 사용하게 된 배경

안드로이드 앱을 개발하면서 RecyclerView를 정말 많이 활용했던 것 같다.
최근에 개발중인 앱에서도 RecyclerView를 사용하고 있는데, 개발하는 도중에 한가지 문제점이 생겼다.

위의 이미지는 최근에 개발중인 앱의 일부 화면이다.
여기서 파란색 LinearLayout은 사용자의 선택에 따라 visible 값이 true 또는 false가 되는데, 파란색 LinearLayout이 보여지는 경우에 맨 아래에 위치한 RecyclerView가 차지하는 공간이 매우 작아진다.

따라서 이렇게 구현하게 되면 앱을 사용하는 입장에서 매우 불편하다고 느끼게 될 것이므로, 이 문제를 반드시 해결해야 했고, 이에 따라 NestedScrollView를 사용하게 되었다.


2. NestedScrollView??

NestedScrollView는 사실 ScrollView이다.
안드로이드 공식 문서에서도 "NestedScrollView is just like ScrollView" 라고 작성되어 있다.
심지어 사용하는 방법도 ScrollView와 다를 것이 별로 없다.

그렇다면 굳이 왜 ScrollView 말고 NestedScrollView를 사용하는가?
이 질문에 대답을 하기 위해서는 먼저 ScrollView를 사용했을 때의 문제점이 무엇인지 알아야 한다.

ScrollView를 사용했을 때의 문제점

아래는 ScrollView안에 RecyclerView를 사용했을 때, 스크롤이 되는 모습이다.

이 경우에 RecyclerView만 스크롤 되다가 더 이상 RecyclerView에서 스크롤을 할 수 없을 때, ScrollView가 스크롤 되는 것을 알 수 있다.

이처럼 ScrollView안에 RecyclerView를 사용하면 상당히 좋지 않은 성능을 보여주는데, 아마도 ScrollViewRecyclerView의 스크롤 이벤트 처리에서 문제가 생겨서 이러한 문제가 발생하지 않을까 라는 생각이 든다. (나중에 더 자세히 알아봐야지)

NestedScrollView를 사용하면 문제 해결!

아래는 NestedScrollView안에 RecyclerView를 사용했을 때, 스크롤이 되는 모습이다.

ScrollView를 사용했을 때와 달리, 모든 View들이 함께 스크롤 되는 것을 확인할 수 있다.


3. NestedScrollView 사용법

NestedScrollView의 사용법은 ScrollView와 다를 것이 없고, 코드는 아래와 같다.

<androidx.core.widget.NestedScrollView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fillViewport="true">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
      
      <!-- 이 부분에 RecyclerView를 비롯한 여러 View들을 배치하면 된다. -->
      <androidx.recyclerview.widget.RecyclerView
          android:id="@+id/recyclerView"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:layout_marginTop="10dp"
          android:nestedScrollingEnabled="false"
          android:overScrollMode="never"
          android:visibility="gone" />
      
    </LinearLayout>
</androidx.core.widget.NestedScrollView>

여기서 한 가지 조심해야 하는 것은 RecyclerViewnestedScrollingEnabled 값을 false로 설정해야 한다는 것이다.

참고로 위의 코드에서 RecyclerViewoverScrollMode 값을 never로 설정한 것은 RecyclerView의 스크롤 효과를 없애기 위해서이다.


4. NestedScrollView 사용을 지양하는 경우

NestedScrollView안에 RecyclerView를 사용할 경우, RecyclerView는 아이템을 전부 미리 생성하게 되고, 이에 따라 뷰를 재사용하여 메모리 효율을 높일 수 있는 RecyclerView의 장점이 사라진다.
따라서 아이템이 많은 경우에는 NestedScrollView안에 RecyclerView를 사용하는 것을 지양하는 것이 좋다고 한다.

그럼 아이템이 많은 경우는 어떻게?

RecyclerView에서 여러 개의 ViewHolder를 사용할 수 있도록 ViewHolder를 관리해주면 된다.

위에서 사용했던 화면을 예시로 들면, 다음과 같은 구조로 구현하면 된다.

따라서 RecyclerViewAdapter 클래스에서는 다음과 같은 작업을 구현하면 된다.

1. override fun getItemViewType(position: Int): Int 함수를 오버라이딩 해서 각각의 아이템에 대하여 다른 Int 값을 리턴한다.
2. override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder 함수에서 viewType(1번에서 리턴한 값)에 따라, 서로 다른 layout을 inflate 한 후, ViewHolder를 생성해서 리턴한다.
3. override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) 함수에서 holder.itemViewType 값에 따라, viewHolder에 데이터를 설정한다.

여러개의 ViewHolder를 사용하는 경우에도 NestedScrollView를 사용했을 때와 같이 자연스럽게 스크롤이 작동함을 확인할 수 있다.

바로 위에 있는 예시의 코드는 Github를 참고하면 될 것 같다.

profile
주니어 안드 개발자

10개의 댓글

comment-user-thumbnail
2022년 2월 13일

안녕하세요! 글 잘 읽고 있습니다.
NestedScrollView를 지양하는 경우에 대한 대응 부분에 조금 이해가 부족한데
마지막 아이템이 많은 경우 하나의 리사이클러뷰에 여러 뷰 홀더 클래스를 두는 경우
1. NestedScrollView 안에 RecyclerView를 넣은 것과 동일하게 스크롤이 동작하나요?
2. 위의 예시에서 각각 뷰홀더의 아이템 리스트에 대한 크기는 1, 1, n개인 건가요?
3. 만약 뷰홀더1, 뷰홀더2의 리스트 크기도 n개면 스크롤이 어떻게 동작되는지 궁금합니다.
제 예상으로는 NestedScrollView사용 시와 동일하게 자연스럽게 위에서부터 하나의 리스트처럼 쫙 펼쳐질 것 같습니다.
4. 혹시 관련 예제 공유해주실 수 있을까요?
질문이 좀 많네요. 좋은 글 감사합니다!

1개의 답글
comment-user-thumbnail
2022년 2월 20일

안녕하세요. 비슷한 것을 만들고 있는 중이라 NestedScrollView 안에 RecyclerView를 넣는 사례를 많이 찾아보게 되어 이곳으로 흘러들어왔는데, 내용이 좋아 댓글 남깁니다.

찾아본 NestedScrollView 안에 RecyclerView를 넣은 모든 사례에서 그렇게 하면 ListView와 같이 모든 리스트 아이템을 일시에 생성하게 되어 Recycler Pattern도 전혀 적용되지 않고 효율이 나쁘다고 설명하던데, 그렇게 만드는 정확한 원인 언급은 찾기 힘들었습니다. 개선해볼만한 방법이 있다 생각하여 해당 가정대로 구현을 진행중이지만 아직 앱이 완성되지 않아 성능 테스트를 해 볼 정도의 단계가 아니라서 의견을 구하고 싶습니다. (질문 내용은 가장 마지막 문단에 있습니다)

RecyclerView가 NestedScrollView 안에 들어가는 경우 NestedScrollView의 LinearLayout은 자녀 항목의 높이합에 해당하는 길이를 지니게 되는데, 실험해 본 결과 RecyclerView에게 높이 값을 특정 숫자로 주지 않는 이상 match_parent든 wrap_content든 자신의 모든 데이터를 불러온 크기에 해당하는 길이로 설정되는 것을 보게 되었습니다. 이것이 RecyclerView의 Recycler Pattern을 망가뜨리는 원인이라고 생각했습니다.

이 글이 훨씬 이해하기 좋아 댓글은 여기서만 달았지만 같은 내용을 StackOverflow에서 먼저 확인했었는데요, 그걸 보니 수년 전에는 wrap_content로 설정해야만 RecyclerView의 높이가 0dp가 아니게 되는 버그가 있었던 모양입니다. android:nestedScrollingEnabled="false"를 넣어주라는 내용도 그곳에 같이 써져 있었는데, 어차피 RecyclerView가 모든 데이터를 불러왔기 때문에 불필요한 스크롤 기능을 없애 두는 목적으로 보였습니다.

제 경우 RecyclerView가 NestedScrollView와 같은 크기가 되게 하고 싶었으므로 OnCreate() 함수 내에서 NestedScrollView에게 OnGlobalLayoutListener를 붙여서 NestedScrollView의 화면상의 크기가 정해지고 나면 RecyclerView의 크기를 NestedScrollView의 크기로 변화시키게끔 코드를 짰습니다. 그렇게 하자 기대대로 동작하더군요. 더미 데이터만으로 성능을 테스트 해 보긴 어려웠지만 스크롤시 onViewRecycled 함수가 실행되는 것으로 보아 Recycler Pattern은 정상적으로 적용중임을 확인할 수 있었습니다.

지금 골머리 앓고 있는 부분은 언제 RecyclerView를 언제 NestedScrollView를 스크롤 하게끔 시킬 것인지 조절하는 것인데, 어느정도 진척은 있기 때문에 의견을 여쭐 내용은 이것이 아닙니다 (다만 방법을 이미 아신다면 조금 도움을 주시면 감사하겠습니다)

제 경우는 NestedScrollView의 크기와 RecyclerView의 크기를 일치시키고 싶었지만, kimbsu00님의 예제의 경우 최상위 레이아웃의 크기에 일치하게끔 하는 정도만으로 충분히 Recycler Pattern이 발동하게끔 만들 수 있을 것 같은데, 그렇게 했을때 성능에 유의미한 차이가 나는지 확인을 해주시거나, 확인이 어려우시다면 성능에 영향은 있을 것으로 보이시는지 의견을 알려주실 수 있으실까요?

2개의 답글
comment-user-thumbnail
2022년 12월 27일

좋은 글 감사합니다! 질문이 있어 덧글을 남기게 되었습니다
3번에 영상에서는 리스트의 형태가 보유 자산, 보유 포트폴리오, 각 코인의 현재 가격으로 보여지는데 (1: 1: N)구조 해당 경우에도 nestedScrollview를 viewType을 3개 가진 Recyclerview 로 대체할 수 잇는것인가요? 4번에 영상의 예시는 N:N:N 구조로 구성되어있는 리스트인것 같아서요!
onBindViewHolder에서 각 viewtype에 대해서 .bind(currentList)의 형식으로 구성할 수 없지 않나 해서 질문드립니다

2개의 답글