본문 바로가기
개발세발

[Android] SnapHelper를 쓸 때엔 onScrollListener 관리를 잘 하자

by 킴부차 2024. 5. 12.

`SnapHelper`는 RecyclerView에 붙어서 마치 RecyclerView를 ViewPager처럼 만들어주는 편리한 클래스이다. SnapHelper는 추상 클래스라 이 자체로는 안쓰이고, 보통은 `PagerSnapHelper`와 같은 실제 구현된 클래스를 사용한다.
PagerSnapHelper를 RecyclerView에 붙이면 스크롤이 끝날 때 자동으로 가장 많이 보여지는 ViewHolder를 중앙에 배치시켜준다.

하지만 많은 개발자든이 RecyclerView에 ScrollListener를 붙여서 사용할 것이다. 로깅을 한다던지, 아니면 스크롤 할 때 효과를 준다던지 여러 이유로.

문제는 ScrollListener 역시 하나의 외부 인스턴스이기에, 메모리 관리를 위해 필요하면 제거도 해주어야 한다.
이 때 우리는 종종 귀찮기 때문에 `removeOnScrollListener()`가 아닌 `clearOnScrollListeners()`를 사용하는데,  `SnapHelper`를 사용하는 RecyclerView에다 `clearOnScrollListeners()`를 호출하면 스냅이 망가져버린다.

이유는 엄청난건 아니고 `SnapHelper` 내부 코드를 까보면 알 수 있는데, 그냥 SnapHelper가 flingListener와 scrollListener를 RecyclerView에 그대로 붙이기 때문이다. 붙은 후엔 따로 관리되는게 아니고 그냥 다른 리스너들과 같이 관리되기 때문에, `clearOnScrollListeners()`를 호출하면 SnapHelper가 붙인 리스너도 같이 날라간다.

아래는SnapHelper.java 코드의 일부이다.

public void attachToRecyclerView(@Nullable RecyclerView recyclerView)
        throws IllegalStateException {
    if (mRecyclerView == recyclerView) {
        return; // nothing to do
    }
    if (mRecyclerView != null) {
        destroyCallbacks();
    }
    mRecyclerView = recyclerView;
    if (mRecyclerView != null) {
        setupCallbacks();
        mGravityScroller = new Scroller(mRecyclerView.getContext(),
                new DecelerateInterpolator());
        snapToTargetExistingView();
    }
}

private void setupCallbacks() throws IllegalStateException {
    if (mRecyclerView.getOnFlingListener() != null) {
        throw new IllegalStateException("An instance of OnFlingListener already set.");
    }
    mRecyclerView.addOnScrollListener(mScrollListener);
    mRecyclerView.setOnFlingListener(this);
}

위 코드처럼, `attachToRecyclerView()`를 호출하면 인자로 넘어온 RecyclerView에다가 Helper 내부의 ScrollListener, FlingListener를 붙일 뿐이다.

하지만 우리는 따로 `addOnScrollListener()`를 호출한게 아니고 그저 SnapHelper를 사용한 것이기 때문에 헷길리기 쉽다.
그러므로 SnapHelper를 쓸 때엔 ScrollListener와 FlingListener를 꼭 clear~~Listener가 아닌 remove~~Listener 등으로 직접 관리해주자. 아니면 날리고 다시 붙이던가.

이걸 몰라서 이슈를 맞다니, 내가 여전히 허접이라는걸 자각할 때마다 인생 참 슬프다는걸 다시 느끼는 하루였다.