Android

이제는 필수가 되어 버린 페이지네이션(Pagination)

c0de_h0ng 2021. 11. 17. 21:15
728x90

페이지네이션이란?

 페이지네이션은 콘텐츠를 여러 개의 페이지에 나눠서 보여주는 UI이다. 페이지가 하단 포지션에 도달하면 다음 페이지를 호출하여 이전 페이지 아래에 계속 데이터를 붙이는 형태이다.

 페이지네이션을 왜 사용하는 가에 대한 의문을 가질 수 있다. 물론 콘텐츠가 적은 경우에는 코드만 길어지므로 굳이 필요 없다고 느끼지만 컨텐츠가 많은 경우에는 상황이 달라진다. 만약 컨텐츠가 갯수가 100개라고 가정해보자. 서버에서 100개의 컨텐츠를 받아올 때의 딜레이, 받은 컨텐츠를 UI에 표현하는 데 걸리는 딜레이, 그리고 유저가 스크롤할 때 퍼포먼스에 대한 버벅임 등 컨텐츠가 많으면 그 만큼 UX적으로 좋지 않아 이때 페이지네이션을 많이 사용한다. 대표적으로 네이버 api, 카카오 api는 페이지네이션을 고려하여 Response를 주고 있다.

페이지네이션을 사용하기 위해서는 크게 2가지의 데이터가 필요하다.

  • 총 페이지 수
  • 페이지마다의 컨텐츠 리스트

페이지네이션은 대표적으로 RecyclerView에서 많이 사용되지만 ScrollView에서도 사용할 수 있다.

설계

기획서에 가격대별 상품 리스트를 페이지네이션의 형태로 만들어 달라고 하였다.

- 페이지네이션 Data Class

앞서 언급했듯이 필수적으로 필요한 총 페이지 수, 페이지마다 콘텐츠 리스트를 포함한 데이터 클래스를 생성한다.

@Parcelize
data class Gift(
    @SerializedName("category")
    val categoryId: String,

    @SerializedName("printName")
    val categoryName: String,

    @Expose
    @SerializedName("totalCount")
    val totalCount: Int,

    @Expose
    @SerializedName("pageCount")
    val pageCount: Int,

    @Expose
    @SerializedName("itemList")
    val giftList: List<GiftInfoModel>?
) : Parcelable
  • totalCount : 총 페이지 수
  • pageCount : 현재 페이지
  • itemList : 컨텐츠 리스트

페이지네이션 할 때마다 서버에서부터 추가로 받는 데이터 List<GiftInfoModel>이다. 그러면 호출할 때마다 새로 받은 리스트를 기존 리스트의 마지막 인덱스 다음에 계속 추가한다.

- 페이지네이션 로직 설계

페이지네이션에서 가장 핵심인 부분은 스크롤이 하단 포지션에 도달했느냐 이다. 사용자가 스크롤을 하는 도중에 새로운 리스트를 호출해 보리면 UX면에서 버벅거림으로 느껴질 수 있다. 따라서 스크롤이 하단 포지션에 도달했을 때 다음 페이지를 호출할 수 있도록 설계해야 한다.

fun onRecyclerViewScrollListener(detectListenerRecyclerView: RecyclerViewScrollDetectListener): OnScrollListener {
    return object : RecyclerView.OnScrollListener() {
        override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            super.onScrolled(recyclerView, dx, dy)
            try {
                val lastVisibleItemPosition = (recyclerView.layoutManager as LinearLayoutManager).findLastCompletelyVisibleItemPosition()
                recyclerView.adapter?.let {
                    val itemTotalCount = it.itemCount - 1
                    if (lastVisibleItemPosition == itemTotalCount) {
                        detectListenerRecyclerView.scrollDetectLastItem()
                    }
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }
}

RecyclerView에는 OnScrollListener라는 인터페이스가 제공하는데 onScrolled와 onScrollStateChanged를 오버라이드 할 수 있다.

  • onScrolled : 스크롤 감지
  • onScrollStateChanged : 스크롤 상태 변환 감지

페이지네이션에서 필요한 함수는 onScrolled이고 해당 함수는 스크롤 중인 RecyclerView, x축, y축 3가지의 매개변수를 가진다. x축 같은 경우에는 스크롤 방향이 Horizontal일 때 사용하고 y축일 경우에는 Vertical 일 때 사용한다.

두 개의 변수를 가지고 값을 비교를 해야 하는데 하나는 findLastCompletelyVisibleItemPosition()에서 하단 마지막 아이템 포지션(lastVisibleItemPosition)이고 또 다른 하나는 화면에 보이고 있는 총아이템의 개수( itemTotalCount)이다. 두 변수의 값이 같을 때 스크롤 포지션이 하단에 도달할 때이므로 값이 같을 때 콘텐츠를 추가 호출한다.

- 추가 호출 리스너 인터페이스

페이지네이션 로직을 재사용하기 위해 리스너를 만들었다.

interface RecyclerViewScrollDetectListener {
    fun scrollDetectLastItem()
}

 

- 추가 호출 api 호출

override fun scrollDetectLastItem() {
    if (viewModel.totalPageCount > 0 && (viewModel.currentPageCount < viewModel.totalPageCount)) {
        viewModel.categoryId?.let {
            showLoading()
            viewModel.giftPriceRangeMore()
        } ?: run {
            hideLoading()
            showToast(R.string.call_merge)
        }
    }
}

추가 호출 Callback을 받았을 때 현재 페이지보다 총페이지가 작을 경우에 추가 호출을 진행하고 현재 페이지가 총 페이지 수보다 크거나 같을 경우에는 모든 페이지를 호출하였으므로 호출하지 않는다. 추가 호출했을 때 Api Response가 성공일 때 현재 페이지에 1을 더한다.

- 추가 호출 콘텐츠 리스트에 추가

viewModel.giftAddListModel.observe(viewLifecycleOwner) {
    giftPriceRangeGiftListAdapter?.addData(it) //추가 호출한 가격대별 선물리스트 추가
}

'Android' 카테고리의 다른 글

안드로이드 4대 컴포넌트  (0) 2021.12.27
BottomSheetDialogFragment에서 Dagger 사용하기  (0) 2021.12.06
디자인 아키텍처 3 - MVC  (0) 2021.11.16
Layout xml 그룹 관리  (0) 2021.11.16
디자인 아키텍처 2 - MVP  (0) 2021.11.16