안드로이드 개발을 하다보면 ScrollView 안에 RecyclerView를 넣어야 하는 경우가 생긴다. 이럴 때, NestedScrollView를 사용하면 된다. 이름처럼 중첩된 스크롤뷰라는 뜻이다. 필자는 NestedScrollView를 아주 유용하게 사용한다. 사용하는 상황은 다음과 같다.

  • RecyclerView이 있는데 다른 형태의 뷰가 보여질 때
  • 타이틀이 있고 아래에 리스트 목록이 보일 때

두 가지 경우는 거의 비슷하다. 첫 번째 경우는 사실 ViewType을 다르게 하여 RecyclerView를 구성할 수도 있다. 하지만, 이 부분이 조금 번거롭다면 NestedScrollView를 사용하는 것도 하나의 방법이다.

issue

이제 필자가 겪은 문제를 말하려고 한다. 서론이 길었다. 위에서 두 번째 경우를 구현해야 하는 상황이 있었다. NestedScrollView는 ScrollView와 마찬가지로 내부에 ViewGroup을 하나만 가질 수 있다. 그래서 LinearLayout을 두고 그 안에 타이틀을 보여주는 TextView와 리스트를 나타내는 RecyclerView를 두었다.

그런데 경우에 따라서 이 NestedScrollView가 자기 멋대로 밑으로 스크롤이 내려갈 때가 있다. 처음에는 이유를 몰랐다. 그래서 구글링을 해보면서 찾아봤다. 이유는 뷰가 그려지면서 안에 넣어둔 또 다른 ScrollView(즉, 여기서는 RecyclerView)에 포커스가 잡히면서 타이틀이 보이지 않는 것이었다.

그래서 이 포커스를 어떻게 없앨까 찾아보았다. 포커스만 없앤다면 뷰가 그려지면서 RecyclerView에 포커스되는 상황이 없어지고 스크롤이 제멋대로 내려가지 않을테니 말이다. 바로 다음의 한줄만 추가하면 된다.

1
android:descendantFocusability="blocksDescendants"

무슨 속성일까?

  • descendantFocusability : ViewGroup 내에서 포커스를 맞출 때 ViewGroup과 그의 하위 뷰의 관계를 설정한다. 여기서 하위 뷰란 자식 뷰를 의미한다.
  • blocksDescendants : 해당 뷰 그룹의 하위 뷰가 포커스를 받지 못하게 하는 옵션이다.

어디에 정의할까?

ScrollView의 자식 뷰에 설정하면 된다. 스크롤뷰는 자식 뷰를 하나만 가지기 때문에 LinearLayout에 설정하면 된다.

xml 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<androidx.core.widget.NestedScrollView
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/achieveAppBar"
app:layout_constraintBottom_toBottomOf="parent">

<LinearLayout
android:descendantFocusability="blocksDescendants"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<TextView
android:layout_marginStart="@dimen/margin_24dp"
android:layout_marginTop="@dimen/margin_16dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/text_my_page_achieve_list_title"
style="@style/SubTitleHeaderStyle"
/>

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/achieveListRv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never"
android:layout_marginTop="@dimen/margin_24dp"/>

</LinearLayout>

</androidx.core.widget.NestedScrollView>

결론

1
android:descendantFocusability="blocksDescendants"

정리하면, 위의 설정은 NestedScrollView 안의 자식 뷰(하위 뷰)가 포커스를 가져가는 현상을 막아준다. 즉, NestedScrollView 안에 있는 RecyclerView가 포커스를 가져가지 않는다. 따라서 NestedScrollView는 원하던 대로 포커스를 받고 스크롤이 내려가는 현상도 방지할 수 있다.

참고