마스터Q&A 안드로이드는 안드로이드 개발자들의 질문과 답변을 위한 지식 커뮤니티 사이트입니다. 안드로이드펍에서 운영하고 있습니다. [사용법, 운영진]

recyclerview 속 recyclerview에서의 페이징 라이브러리 구현

0 추천

Paging Library를 이용해서 데이터를 불러오는데 그냥 RecyclerView 에서는 잘 되는데 NestedScrollView나 RecyclerView 안에서 사용하면 모든 페이지가 불러와 지더라구요. 그리고 아이템 재활용도 안되는 것 같습니다. 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/section_list"
        android:layout_width="match_parent"
        android:nestedScrollingEnabled="false"
        android:layout_height="wrap_content" />
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_height="wrap_content">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/content_title"
            fontPath="@string/NanumBarunGothicBold_path"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="12.3dp"
            android:layout_marginTop="20dp"
            android:layout_marginBottom="20dp"
            android:includeFontPadding="false"
            android:text="@string/text_applied_company"
            android:textColor="@color/white"
            android:textSize="30sp" />

        <TextView
            android:id="@+id/manage_btn"
            fontPath="@string/NanumBarunGothicUltraLight_path"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignBaseline="@id/content_title"
            android:layout_alignParentEnd="true"
            android:layout_marginEnd="14dp"
            android:foreground="?attr/selectableItemBackground"
            android:includeFontPadding="false"
            android:text="@string/text_manage"
            android:textColor="@color/white"
            android:textSize="12sp"
            android:visibility="gone" />
    </RelativeLayout>

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/content_list"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="@dimen/margin_content_list"
            android:layout_marginTop="12dp"
            android:layout_marginEnd="@dimen/margin_content_list"
            android:layout_marginBottom="10dp" />

        <FrameLayout
            android:id="@+id/empty_view"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:visibility="gone">

            <com.airbnb.lottie.LottieAnimationView
                android:id="@+id/empty_anim"
                android:layout_width="300dp"
                android:layout_height="300dp"
                android:layout_gravity="center"
                app:lottie_fileName="lottie/not_found.json"
                app:lottie_loop="true"
                app:lottie_scale="0.2" />

            <TextView
                fontPath="@string/NanumBarunGothicBold_path"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:layout_marginTop="30dp"
                android:text="@string/empty_text" />
        </FrameLayout>
    </FrameLayout>

</LinearLayout>

프래그먼트 부분 XML과 아이템 XML(최상단 RecyclerView)

fun bind(section: Section) {
        content_title.text = section.title
        if (section.viewType == VIEW_MOVIE) {
            manage_btn.visibility = View.GONE
            val gridLayoutManager = GridLayoutManager(context, 2)
            content_list.layoutManager = gridLayoutManager
            content_list.addItemDecoration(MovieItemDecoration(context as Activity?))
            val config = PagedList.Config.Builder()
                    .setInitialLoadSizeHint(PAGE_SIZE)
                    .setPageSize(PAGE_SIZE)
                    .build()

            val factory = object : DataSource.Factory<Int, Movie>() {
                override fun create(): DataSource<Int, Movie> {
                    val movieApi = MovieApi.create()
                    return TMMPageKeyDataSource(movieApi = movieApi, region = Util.getRegionCode(context))
                }
            }

            val builder = RxPagedListBuilder(factory, config)
            movieAdapter = MoviePagedListAdapter({ view, position ->
                val intent = Intent(context, DetailActivity::class.java)
                intent.putExtra("movie_id", movieAdapter.getItem(position)?.id)
                val poster = Pair.create(view.findViewById<View>(R.id.movie_list_recyclerview_poster), ViewCompat.getTransitionName(view.findViewById(R.id.movie_list_recyclerview_poster)))
                val options = ActivityOptionsCompat.makeSceneTransitionAnimation((view.context as Activity), poster)
                context.startActivity(intent, options.toBundle())
            }, object : OnEmptyListener {
                override fun onEmpty() {
                    setMovieRecyclerEmpty(true)
                }

                override fun onNotEmpty() {
                    setMovieRecyclerEmpty(false)
                }
            })
            content_list.adapter = movieAdapter
            builder.buildObservable()
                    .subscribe {
                        movieAdapter.submitList(it)
                    }
        }
        if (section.viewType == VIEW_COMPANY) {
            manage_btn.visibility = View.VISIBLE
            manage_btn.setOnClickListener(View.OnClickListener { v: View? -> context.startActivity(Intent(context, FavoriteListActivity::class.java)) })
            val linearLayoutManager = LinearLayoutManager(context)
            linearLayoutManager.orientation = RecyclerView.HORIZONTAL
            content_list.layoutManager = linearLayoutManager
            companyAdapter = FragmentCompanyFavoriteRecyclerViewAdapter(FavoriteCompanyDataLocalSource.getInstance().getFavoriteCompany(context), { view, position ->
                val intent = Intent(view.context, MovieListActivity::class.java)
                intent.putExtra("keyword", companyAdapter.getItem(position))
                view.context.startActivity(intent)
            }, object : OnEmptyListener {
                override fun onEmpty() {
                    setCompanyRecyclerEmpty(true)
                }

                override fun onNotEmpty() {
                    setCompanyRecyclerEmpty(false)
                }
            })
            content_list.adapter = companyAdapter
        }
    }

최상단 RecyclerView의 뷰홀더에서 바인딩 시켜주는 부분

구현하고자 하는 화면이고 위에있는 Horizental 리스트는 로컬에서 불러오는 거고 아래 예정작 리스트가 페이징 하려고 합니다.

프로그래밍잘하고싶은나 (390 포인트) 님이 2021년 1월 3일 질문
프로그래밍잘하고싶은나님이 2021년 1월 7일 수정
NestedScrollView를 쓰신 부분이 안보이네요. 혹시 fillViewPort="true"는 추가하셨나요?
NestedScrollView를 사용했다가, "네스티드 스크롤뷰를 사용하게 되면 뷰홀더는 재활용 되지 않고, 어플리케이션 성능을 크게 저하시키는 단점을 갖고 있다"는 https://gamjatwigim.tistory.com/102 이분의 글을 보고 리사이클러뷰를 중첩해서 구현하게 되었습니다.

1개의 답변

0 추천
 
채택된 답변
제가 알고 있기로, 페이징을 할 때 이중 RecyclerView는 필요하지 않습니다.
Activity나 Fragment에 하나의 RecyclerView로 다 처리할 겁니다.
RecyclerView 안에 RecyclerView가 있는 경우
즉 Adapter에서 RecyclerView가 있는 경우는
예를들어
인스타그램 유저에 대한 메인 정보 하단에 유저의 최근 포스팅이 리스트로 3개씩 나타나게 보여야 한다.
뭐 그럴 때 사용하는 겁니다.
그러면 유저목록을 가져오고, 유저각각의 최근 포스팅 목록이 하단에 나와야 하기 때문에
그럴때만 저는 이중 RecyclerView를 사용했지..
하나의 리스트를 페이징할 때는 액티비티나 프레그먼트에 단 하나의 RecylerView를 가지고 활용합니다.
아래 구글 문서를 좀 더 보시거나 유사 샘플들을 다운로드 받아 실행해 보시고,
동작이 되는 샘플을 참고해서 수정해서 구현하시길 제안드립니다.

https://developer.android.com/topic/libraries/architecture/paging?hl=ko
Will Kim (43,170 포인트) 님이 2021년 1월 4일 답변
프로그래밍잘하고싶은나님이 2021년 1월 19일 채택됨
그럼 RecyclerView를 하나만 구현하려면 타이틀, 리스트, 타이틀, 리스트 이런식으로 구현하면 되는 건가요? 혹시 NestedScrollView 를 이용해서 Fragment 안에 리사이클러뷰 두개로 구현을 할 수 있을까요?
아 전에는 이미지가 없었던 것으로 기억하는데 이미지가 추가가 되었네요.
저 스틸컷만으로는 이해가 안가는데,
등록제작사 하단의 이미지 목록은 RecyclerView로 LinearLayout Horizontal 처리하신 것일 거고,
그 밑에 예정작이 각 행마다 Horizontal Scroll 되어야 하는건가요?
그러면 각 행마다 RecyclerView가 이중으로 처리되는게 맞긴 한데요.
일단 올린 이미지를 이해해야 하니까 질문드립니다.
이미지에 대한 설명을 드리자면, 등록 제작사 아래는 가로로 스크롤 되는 것이 맞고 예정작은 스판 사이즈 3인 GridLayout으로 구현되고 세로로 스크롤 되는 것입니다.
스샷을 보면, 제가 볼 때는
최상단 레이아웃은
등록제작사하단에
RecyclerViewTop 가 위치하고
그 아래에
예정작 밑에
RecyclerViewUpcoming 이렇게 두개의 리사이클러 뷰를 만듭니다.

어댑터는 두개가 있어야 겠지요.
위의 횡스크롤 리사이클러뷰를 위한 어댑터1개

그리고 하단은 저라면 아래처럼 StaggeredGridView를 하단에 넣어서 3개를 구성할 겁니다.

recyclerUpcoming.setLayoutManager(new StaggeredGridLayoutManager(3,
                StaggeredGridLayoutManager.VERTICAL));

그리고 이 recyclerUpcoming에 대한 어댑터가 하나 있고요.
이게 되면 일단 한페이지를 가져와서 보여지나고 보고
그 다음에는 recyclerUpcoming의 현재 보여지는 아이템의 index를 보고 페이징 기능을 추가하면 됩니다. (ScrollView 필요없고, recyclerview 자체의 스크롤뷰만 필요합니다.)
그러나 혹시 전체적으로 스크롤이 되어야 한다면, 전체를 감싸는 ScrollView가 있고, RecyclerView의 MaxHeight를 세팅해서 너무 많이 스크롤되지 않게 조절하면 될 것 같네요.
스크롤을 하는 방법은 매우 많기 때문에, 스크롤을 어디까지 허용할 것인가에 따라서 미세 조정은 어쩔 수가 없습니다.
즉, 전체 화면에서 상단 제작사 횡스크롤 Recycler뷰는 위로 사라져야 하지만, 아래 스크롤은 결국 이중이 되기 때문에, 하단의 RecyclerView가 너무 길어지면, 스크롤을 관리하기 어려워집니다.
경험상 스크롤에 대한 관리는 RecyclerView에 일임하고,
다른 부분은 전체 UI의 구성을 맞추거나 애니메이션 정도 까지 커버하는게 맞다고 생각합니다.
답변 감사드립니다. 말씀해주신대로 리사이클러 두개로 구현하고 밖에 NestedScrollView로 감싸서 addOnScrollChangedListener로 직접 페이징 하면서 페이징 라이브러리 만큼은 아니지만 나름 잘 구현되었습니다. 답글 보면서 마지막 두줄이 잘 이해되지 않아서 질문드립니다. 문제 해결에 도움을 주셔서 정말 감사드립니다.
화면 디자인에 따라서
리싸이클러 뷰가 제목 하단에 거의 전체 화면을 덮고 있다면,
굳이 ScrollView 없이, 리싸이클러 뷰 자체의 스크롤 기능으로 하는 게 자연스럽습니다.

그러나 경우에 따라선 스크롤 요소가 많은 화면도 있고,
상단에 정보가 많고,
하단의 공간이 작아서 리사이클러뷰 사이즈 내에서 스크롤하면 답답해 보이죠.
그런 경우에는 보통 리사이클러 뷰가 위의 케이스처럼 어느 정도 커질 때까지는 외부의 ScrollView를 이용해서 스크롤해서 위로 올리고
그 다음부터는 리사이클러 내부 스크롤을 이용해서 스크롤해야 자연스럽습니다.

물론 리싸이클러뷰를 wrap-content로 해서 외부 스크롤로만 처리할 수도 있습니다만, 그러면 스크롤 컨트롤이 복잡해 지죠. 추가 로딩을 자연스럽게 하려면 말입니다. 버튼 같은 걸로 하면 문제는 없겠지만요.

그런 케이스들을 모두 다 설명할 수는 없지만,
아무튼 그렇게 할 때 애니메이션까지 써서 더 화려하게 만드는 경우도 있습니다.

어떤 기준을 정하고 프로그래밍을 해야 하는데,
저의 경우는 RecyclerView의 스크롤 기능을 최대한 활용합니다.
이럴땐 이렇게 코딩하고, 저럴땐 저렇게 코딩하는 것은
이후에 소스를 분석하기도 어려워질테니까요.
일단, 마지막 두줄은 신경쓰지 마시고요,
찾아 보니까 유투브 강좌는 있는데, github 소스는 공개 안한 것같네요.
페이지네이션까지 있는 좋은 강좌가 있으니 참고 해 보세요.

https://www.youtube.com/watch?v=tiXP__iYtq4
...