본문 바로가기
별밤 일지/개발

[Android] Viewpager2, Scrollview 터치와 스크롤 분리하기

by 별밤 에디터 2 2023. 11. 29.

인스타그램이나 페이스북 등의 소셜 앱들을 보면, 일반적으로 한 게시물 안에 여러 이미지를 좌우로 스크롤 할 수 있고, 각각의 게시물들은 위 아래로 스크롤 할 수 있게 되어있는 모습을 볼 수 있다. 별 헤는 밤 메인 페이지도 이러한 방식으로 유저들이 작성한 별자리 관측 게시물들을 홈 화면에서 보여주고 있다.

소셜 앱에서의 스크롤 화면(왼쪽)과 별 헤는 밤에서의 스크롤 화면(오른쪽)

 

하지만 늘 그렇듯 당연한 기능임에도 구현은 어렵다... 대표적으로 메인 페이지를 위 아래로 스크롤 할 때, 스크롤 동작을 게시글의 이미지 터치로 인식하여 게시글 상세 페이지로 이동하는 문제가 발생하곤 한다.

 

이번 글에서는 이러한 ViewPager2의 터치 이벤트와 Scrollview의 스크롤 이벤트 충돌로 인해 발생하는 문제와 해결 방법을 알아보도록 하자.

 

 

메인 화면 레이아웃 구조

ViewPager2 vs Recyclerview

이미지를 좌우로 스와이프 할 수 있게 하는 데에는 2가지 방법이 있다.

1. android:orientation="horizontal" 으로 Recyclerview 방향을 좌우로 설정하여 사용한다.
2. ViewPager2 컴포넌트를 사용한다.

 

ViewPager2는 Android studio에서 이미지 슬라이드에 사용되는 레이아웃이다. 또한 Recyclerview 기반으로 만들어져서 Recylcervie.adapter를 사용한다. 따라서 둘은 거의 비슷하게 보이며 사용법도 매우 유사하다.

 

그러나 막상 사용해보면 둘의 차이점이 확실하게 보인다. Recyclerview는 아이템들이 모두 연결되어 있는 것처럼 스크롤 하다가 스크롤을 멈추면 멈춘 그 자리에서 스크롤이 정지된다. 반면 ViewPager2는 아이템이 서로 구분되어 있어 일정 부분 이상 스크롤하면 자동으로 다음 아이템으로 넘어간다. 또 Recyclerview는 아이템 추가, 삭제, 수정 시 적용이 빠르지만, ViewPager2는 아이템 추가, 삭제, 수정 시 적용이 느리거나 어렵다.

사진을 절반 이상 넘기면 자동으로 다음 사진으로 넘어간다. (ViewPager2)

정리
  • Recyclerview : 아이템 변화가 많거나, 매끄러운 스크롤을 보여주고 싶을 때 사용.
  • ViewPager2 : 아이템이 고정적인 경우, 분리된 리스트를 보여주고 싶을 때 사용.

 

이런 특징들을 비교하여 별 헤는 밤에서는 ViewPager2를 사용하게 되었다.

 

 

메인 프래그먼트 구조

메인 프래그먼트의 레이아웃 구조를 살펴보면 다음과 같다.

 

NestedScrollview안에 Recyclerview를 배치하고, Recyclerview item 레이아웃안에 ViewPager2를 포함시켰다.

이런 방식을 사용하면 NestedScrollview안에 있는 Recyclerview에 adapter가 1개 필요하고, ViewPager2에 adapter가 1개 필요하여 2개의 adapter를 중첩해서 사용하게 된다.

 

문제의 원인

  • Ryclerview Adapter에서 ViewPager2에 setOnClickListener 선언
  • ViewPager2의 Adapter에서 imageview에 setOnClickListener 선언

여기에 문제의 원인이 있었다. 2개의 adapter에 모두 클릭 이벤트를 설정해 놨던 것이다. 그래서 조금만 터치해도 2개의 setOnClickListener가 호출되니 스크롤이 되지 않고 터치 이벤트가 발생했었다.

 

public static class ViewHolder extends RecyclerView.ViewHolder {
        ViewPager2 mainPost;

        public ViewHolder(View itemView, final OnMainPostClickListener listener) {
            super(itemView);
        
            mainPost= itemView.findViewById(R.id.mainPost);
        }

        public void setItem(MainPost item) {

            mainPost.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent intent = new Intent(v.getContext(), PostActivity.class);
                    intent.putExtra("postId", item.getPostId());
                    ((Activity) context).startActivity(intent);
                }
            });

Recylcerview adapter

@Override
    public void onBindViewHolder(@NonNull MainPostSliderAdapter.MyViewHolder holder, int position) {
        holder.bindSliderImage(sliderImage.get(position));
        holder.mImageView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(v.getContext(), PostActivity.class);
                intent.putExtra("postId", postId);
                ((Activity) context).startActivity(intent);
            }
        });
    }

viewPager2 Adapter

 

 

해결 : 터치 이벤트 정리

제일 먼저 Recylcerview.adapter에 있는 setOnclickListener부터 수정해보자.

ViewPager2에 setOnclickListener를 설정했기 때문에 스와이프 이벤트가 불러지기도 전에 페이지 이동이 발생했다고 판단했다. 따라서 ViewPager2가 아닌 부모 레이아웃인 ConstraintLayout에 setOnclickListener를 설정해 주었다.

ViewPager2 mainPost; -> ConstraintLayout mainPost;

 

그리고 viewPager2의 adapter에 있는 Imageview.setOnclickListener는 불필요하기 때문에 삭제해주었다. 하지만 이 경우에는 아래 이미지의 노란색 부분만 터치 했을 때 클릭 이벤트가 발생했다.

@Override
    public void onBindViewHolder(@NonNull MainPostSliderAdapter.MyViewHolder holder, int position) {
        holder.bindSliderImage(sliderImage.get(position));
       // holder.mImageView.setOnClickListener(new View.OnClickListener() {
       //     @Override
       //     public void onClick(View v) {
       //         Intent intent = new Intent(v.getContext(), PostActivity.class);
       //         intent.putExtra("postId", postId);
       //         ((Activity) context).startActivity(intent);
       //     }
     //   });
    }

ViewPager2 Adapter(ImageView 클릭 이벤트 주석처리)

 

recyclerview.adapter에만 클릭 이벤트를 설정한 경우

 

예상과 다르게 ViewPager2의 클릭 이벤트는 부모 레이아웃의 클릭 이벤트에 영향을 받지 않는 것 같았다. 따라서 기존 코드대로 ViewPager2.Imageview에 클릭 이벤트를 설정해서 마무리를 할 수 있었다.

 

 

정리

아무래도 ViewPager2 클릭 이벤트 코드를 짜고 난 후에, 터치 이벤트 코드를 잊어버리고 메인 프래그먼트의 Recyclerview.adapter에 중복으로 구현하게 된 것 같다. 페이지 별로 매번 코드를 중간 중간 추가하다 보니 발생한 실수였다. 이번 실수에서 업데이트나 수정사항이 있는 경우에는 꼭 commit 이나 코드 옆에 주석을 남겨놓는 습관을 길러야겠다고 다짐했다. 주석 적을 때는 잠깐 귀찮더라도 오류가 발생했을 때 파악이 쉬우니까 꼭 합시다. (깔끔한 유지 보수가 좋은 어플을 만든다..!)

 

다음 글에서는 방위각과 고도를 이용해 카메라에 이미지를 띄우는 기능에 대해 정리해 볼 예정이다.