Android/에러 및 문제 해결

[Android] RecyclerView 스크롤 리스너로 무한 스크롤 처리하기

nueijeel 2025. 6. 4. 23:44

 

안드로이드에서 리스트를 표시할 때 사용자 경험을 위해 무한 스크롤을 사용한다.

 

 

무한 스크롤(Infinite Scroll)이란?

사용자가 페이지 하단에 도달했을 때 목록이 계속 로드되는 방식으로

다음 콘텐츠를 보기 위해 추가적인 동작이 없고, 페이지 로드 시간이 짧다는 장점이 있다.

 

아무래도 모바일 환경에서는 화면이 작기 때문에

별도의 동작 없이 사용자 입장에서 매끄럽게 작동하는 것이 중요하다.

 

 

 

 

안드로이드에서는 Jetpack Paging3 라이브러리를 이용해 무한 스크롤을 구현할 수도 있지만

이번 글에서는 RecyclerView의 Scroll 리스너를 사용해 간단히 처리하는 방식을 소개하려고 한다.

 

 

 

리스트 불러오는 API 구조

// 필터 미지정 시 모든 쿼리 값 안넣고 호출
@GET("boards/list")
suspend fun getBoardList(
    @Query("gameStartDate") gameStartDate: String? = null,
    @Query("maxPerson") maxPerson: Int? = null,
    @Query("preferredTeamIdList") preferredTeamIdList: Array<Int>? = null,
    @Query("page") page: Int? = null,
): Response<GetBoardListResponseDTO?>

 

api 호출 시 쿼리로 page 값을 넘겨 해당하는 부분만큼의 데이터를 받아오게 되기 때문에

page값을 증가시켜 스크롤 시점에 호출해 다음 분량의 데이터를 받아올 수 있다. (size는 고정됨)

 

 

 

RecyclerView 스크롤 리스너

 

사용자가 스크롤로 현재 리스트의 끝에 도달했는지를 감지하기 위해 RecyclerView의 addOnScrollListener() 메서드를 이용해 스크롤 위치를 실시간으로 체크할 수 있다.

 

 

addOnScrollListener()

RecyclerView는 내부적으로 스크롤이 발생할 때 마다 RecyclerView.OnScrollListener를 통해 콜백을 제공한다.

addOnScrollListener(
    object : RecyclerView.OnScrollListener() {
        override fun onScrolled(
            recyclerView: RecyclerView,
            dx: Int,
            dy: Int,
        ) {
            super.onScrolled(recyclerView, dx, dy)
            val lastVisibleItemPosition =
                (recyclerView.layoutManager as LinearLayoutManager)
                    .findLastCompletelyVisibleItemPosition()
            val itemTotalCount = recyclerView.adapter!!.itemCount

            if (lastVisibleItemPosition + 1 >= itemTotalCount && !isLastPage && !isLoading) { // 새로운 목록 불러와야함
                currentPage += 1
                getBoardList()
            }
        }
    },
)

 

- dx : 수평으로 얼마나 스크롤됐는지 (양수일 경우 오른쪽 스크롤)

- dy : 수직으로 얼마나 스크롤됐는지 (양수일 경우 아래로 스크롤)

 

보통 세로 스크롤 기반으로 리스트가 구성되므로 dy를 기준으로 처리된다.

 

- findLastCompleteVisibleItemPosition() : 현재 화면에 완전히 보이는 마지막 아이템의 adapter position 반환

- itemCount : 현재 어댑터에 표시되고 있는 전체 아이템의 수

 

위 값들로 현재 어댑터의 마지막 아이템 position과 전체 아이템 수를 구한다.

 

 

 

여기서 lastVisibleItemPosition값에 1을 더해 itemTotalCount와 비교하는 이유는 해당 인덱스가 0부터 시작하는 값이기 때문이다.

(만약 좀 더 여유를 두고 리스트를 미리 불러오려면 itemTotalCount에 도달하기 전으로 조건을 설정해주거나 findLastVisibleItemPosition() 메서드로 아이템 인덱스를 가져오는 방법이 있다.)

 

isLastPage는 api 호출 시 서버에서 전달받은 마지막 페이지 여부이고 isLoading은 중복 요청 방지를 위한 값이다.

 

 

 

조건문을 만족하는 경우에만 새 페이지 로드가 가능하기 때문에

페이지 값을 저장하는 변수를 증가시킨 뒤 리스트를 불러오는 함수를 호출하였다.

 

 

 

이렇게 하면 불러온 스크롤 중 마지막 데이터가 화면에 표시되었을 때 

리스너 콜백에 의해 이후 데이터를 불러와 목록에 추가하게 된다.

 

 

 

마무리

 

RecyclerView 스크롤 리스너를 사용해 간단히 스크롤 처리를 구현해보았지만,

로딩 상태나 에러 핸들링, 캐싱 등을 다루기 위해서는 Paging 라이브러리가 더 적합할 수 있다.

 

추후 코드 리팩토링을 통해 Paging 라이브러리를 적용하는 방법도 소개해보겠다.

 

 

 

 

 

728x90