프로그래밍/Android

[안드로이드] Nested RecyclerView 구현시 스크롤 문제 해결하기

Lou Park 2021. 3. 8. 17:03

OrientationAwareRecyclerView로 구현한 화면

 

Horizontal, Vertical 방향이 혼재된 Nested RecyclerView를 구현하다 보면

세로 스크롤을 하는 도중 가로 스크롤로 넘어가는데 자연스럽지 않거나 그 반대 상황이 생긴다.

 

그럴때 기본 RecyclerView 대신 아래의 커스텀 RecyclerView를 사용하면 조금 더 자연스러워 진다.

간단하게 주석으로 무슨일을 하는지 적어두었다.

원 코드 출처는 여기이다.

 

public class OrientationAwareRecyclerView extends RecyclerView {

    private float lastX = 0.0f;
    private float lastY = 0.0f;
    private boolean scrolling = false;

    public OrientationAwareRecyclerView(@NonNull Context context) {
        this(context, null);
    }

    public OrientationAwareRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public OrientationAwareRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs,
                                        int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        addOnScrollListener(new OnScrollListener() {
            @Override
            public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                scrolling = newState != RecyclerView.SCROLL_STATE_IDLE;
            }
        });
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent e) {
        final LayoutManager lm = getLayoutManager();
        if (lm == null) {
            return super.onInterceptTouchEvent(e);
        }

        boolean allowScroll = true;

        switch (e.getActionMasked()) {
            case MotionEvent.ACTION_DOWN: {
                lastX = e.getX();
                lastY = e.getY();
                // If we were scrolling, stop now by faking a touch release
                // 아마 Fling시에 손가락 터치로 멈추는거일듯...
                if (scrolling) {
                    MotionEvent newEvent = MotionEvent.obtain(e);
                    newEvent.setAction(MotionEvent.ACTION_UP);
                    return super.onInterceptTouchEvent(newEvent);
                }
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                // We're moving, so check if we're trying
                // to scroll vertically or horizontally so we don't intercept the wrong event.
                float currentX = e.getX();
                float currentY = e.getY();
                float dx = Math.abs(currentX - lastX);
                float dy = Math.abs(currentY - lastY);
                // 세로로 스크롤 할려고할경우 lm 이 세로 스크롤 가능한지 여부, 가로로 할경우 가로 스크롤 가능여부에 따름.
                // 만약 가로로 스크롤 하지 못하는 rv인데 가로로 스크롤 하려고 할 경우 Touch Event를 가로채는걸 막는다.
                allowScroll = dy > dx ? lm.canScrollVertically() : lm.canScrollHorizontally();
                break;
            }
        }

        if (!allowScroll) {
            return false;
        }

        return super.onInterceptTouchEvent(e);
    }

}