0. 요약
- 최근 고민은 리사이클러뷰의 재활용과 함께, NestedScrollView를 사용하지 않고, 뷰 구조를 잡기 위해서 고민을 했다.
- 고민 끝에, Coordinator Layout 내부에 각종 View를 셋팅하게 되면, 리사이클러뷰의 구조를 살린채, 레이아웃 구조를 잡을 수 있을 것이라고 생각을 했다.
- 하지만, Coordinator Layout에서 자유롭게 사용되어지는 뷰는 behavior를 받는 뷰 뿐만이 자유롭게 움직일 수 있다는 것을 알았다.
- 그래서, 리사이클러뷰를 만들고, 어댑터에 여러 개의 뷰홀더를 만들어야만 해결이 된다는 것을 알게 되었다.
1. NestedScrollView & RecyclerView
- 리사이클러뷰의 장점은 뷰홀더를 통해서 뷰를 재활용해서, 앱의 퍼포먼스를 향상시키는 것에 있다.
- 하지만, 리사이클러뷰가 전부가 아닌, 한 부분으로만 뷰구조를 잡아야 하는 경우가 발생하게 된다.
- 그런 상황에서 가장 쉽게 생각할 수 있는 것은 네스티드 스크롤뷰를 리사이클러뷰에 감싸는 것을 많이 사용한다.
- 하지만, 네스티드 스크롤뷰를 사용하게 되면 뷰홀더는 재활용 되지 않고, 어플리케이션 성능을 크게 저하시키는 단점을 갖고 있다.
2. Coordinator Layout을 사용하면 해결할 수 있지 않을까?
처음에는 Coordinator Layout을 사용하게 되면 해당 문제를 해결할 수 있을 것이라고 생각을 했다. 그래서 처음 생각한 뷰의 구조는 아래와 같다.
- CoordinatorLayout을 사용하여, 스크롤 시에 툴바 영역과 TextView, ConstraintLayout, RecyclerView 영역처럼 다양한 View들이 공존하는 영역으로 나눈다.
- 리스트를 스크롤 시에 CoordinatorLayout을 이용해서, 툴바의 애니메이션을 사용한다.
- NestedScrollView가 갖는 재활용이 되지 않는 리스트에서 벗어난다.
3. CoordinatorLayout + RecyclerView 구조
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 | <?xml version="1.0" encoding="utf-8"?> <layout> <data> </data> <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:bind="http://schemas.android.com/tools" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- 툴바 관련 --> <include layout="@layout/layout_coordinator_toolbar"/> <LinearLayout android:layout_width="match_parent" android:layout_height="320dp"> <!-- 내용들 --> </LinearLayout> <!-- 리스트 --> <androidx.recyclerview.widget.RecyclerView android:id="@+id/rv_contents" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingStart="@dimen/content_card_side_dimen" android:paddingEnd="@dimen/content_card_side_dimen" app:layout_behavior="@string/appbar_scrolling_view_behavior"/> </androidx.coordinatorlayout.widget.CoordinatorLayout> </layout> | cs |
우선, Coordinator가 XML의 최상단에 있다.
3-1. 평상시처럼 NestedScrollView처럼 생각하는 문제
include_coordinator_toolbar 영역을 툴바 영역을 별도로 분리를 시켰고, LinearLayout에는 TextView나 ConstraintLayout등의 다양한 뷰가 사용되도록 했고, 리사이클러뷰를 추가적으로 사용하는 구조를 생각했다.
이런 구조를 생각한 이유는 평상시에 NestedScrollView로 XML을 만들었기 때문에 이와 같은 구조를 생각한 것 같다. 하지만, 이것은 위에서 말한 것처럼 치명적인 문제가 있다.
- NestedScrollView은 리사이클러뷰가 오더라도, 해당 뷰를 모두 미리 그려서 재활용의 장점이 없이, 뷰를 표현한다.
- 그래서 NestedScrollView는 TextView, LinearLayout, ConstraintLayout, RecyclerView등 구분 없이 뷰를 모두 그린다.
- 이러한 뷰 구조는 성능상의 이슈를 가져올 가능성이 크다.
3-2. 위의 XML의 문제
3-3. 문제 해결 방법
4. RecyclerView에 여러 개의 ViewHolder를 만들어서 해결하자.
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | <?xml version="1.0" encoding="utf-8"?> <layout> <data> <variable name="subtitle" type="String"/> </data> <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:bind="http://schemas.android.com/tools" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/view_content_background"> <!-- 툴바 관련 --> <com.google.android.material.appbar.AppBarLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="194dp" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" android:fitsSystemWindows="true"> <com.google.android.material.appbar.CollapsingToolbarLayout android:layout_width="match_parent" android:layout_height="match_parent" app:layout_scrollFlags="scroll|enterAlwaysCollapsed" android:fitsSystemWindows="true" app:contentScrim="?attr/colorPrimary" app:title="안녕."> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/colorPrimary" android:fitsSystemWindows="true" app:layout_collapseMode="parallax"/> <androidx.appcompat.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:popupTheme="@style/ThemeOverlay.AppCompat.Light" app:layout_collapseMode="pin" /> </com.google.android.material.appbar.CollapsingToolbarLayout> </com.google.android.material.appbar.AppBarLayout> <androidx.recyclerview.widget.RecyclerView android:id="@+id/rv_contents" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"/> </androidx.coordinatorlayout.widget.CoordinatorLayout> </layout> | cs |
4-1. Adapter
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | class ContentAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() { enum class ContentViewType(val num: Int) { HEADER(0), CONTENT(1), HASHTAG(2), EMPTY(5) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return when (viewType) { ContentViewType.HEADER.num -> { TitleViewHolder(makeInflater(parent, R.layout.item_content_title)) } ContentViewType.CONTENT.num -> { ContentViewHolder(makeInflater(parent, R.layout.item_content_content)) } ContentViewType.HASHTAG.num -> { SubTitleViewHolder(makeInflater(parent, R.layout.item_content_subtitle)) } else -> { assert(false) { "절대 안 옴" } ContentViewHolder(makeInflater(parent, R.layout.item_content_subtitle)) } } } override fun getItemCount(): Int { return 5 } override fun getItemViewType(position: Int): Int { return when (position) { 0 -> ContentViewType.HEADER.num 1, 2, 3 -> ContentViewType.CONTENT.num 4 -> ContentViewType.HASHTAG.num else -> ContentViewType.EMPTY.num } } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { } inner class TitleViewHolder(view: View) : RecyclerView.ViewHolder(view) inner class ContentViewHolder(view: View) : RecyclerView.ViewHolder(view) inner class SubTitleViewHolder(view: View) : RecyclerView.ViewHolder(view) private fun makeInflater(parent: ViewGroup, layout: Int): View = LayoutInflater.from(parent.context).inflate(layout, parent, false) } | cs |
4-2. 설명
어댑터에서는 헤더, 콘텐츠, 다른 부가적인 뷰, 바텀 뷰 등을 각각의 뷰홀더에서 관리를 해줘야 한다. 그러면 결과적으로 봤을 때는 리사이클러 뷰 이하에, 다양한 뷰가 움직이는 구조가 된다.
그럼으로, 다양한 뷰를 그릴 수 있고, 리사이클러 뷰의 성능상 이점을 그대로 사용할 수 있게 되었다.
'Android 공부 > Android UI' 카테고리의 다른 글
안드로이드 - Jetpack Navigation 사용 [코드 리팩토링] (0) | 2020.04.04 |
---|---|
RecyclerView in SwipeRefreshLayout with CoordinatorLayout (0) | 2019.09.13 |
[안드로이드 UI 공부] Android Shared-Element Transitions - 2 (0) | 2019.04.12 |
[안드로이드 UI 공부] Android Shared-Element Transitions - 1 (0) | 2019.04.10 |
Android Motionlayout 삽질기 (0) | 2019.03.25 |