본문 바로가기
안드로이드 자바

[Java][Android] 뷰페이저2 (viewPager2) 무한 스크롤 / 슬라이딩 적용

by teamnova 2024. 7. 14.
728x90

https://stickode.tistory.com/1190

 

지난 게시글인 [Java][Android] 뷰페이저2 (viewPager2) 구현 + 이미지 순서 표시

에서는 뷰페이저2를 사용하여 이미지를 표시하는 방법에 대해 알아보았습니다.

 

이번 포스트에서는 뷰페이저2에 무한 스크롤 / 슬라이딩 기능을 적용하여 사용자가 마지막 아이템에 도달했을 때, 다시 첫 번째 페이지로 넘어가도록 만드는 방법을 알아보겠습니다.

 

일반적으로 뷰페이저는 사용자가 마지막 페이지에 도달하면 더 이상 넘어가지 않지만, 무한 스크롤 기능을 구현하면 페이지가 계속 순환하여 사용자에게 보다 편리한 경험을 제공할 수 있습니다.

 

우선, 전체 코드입니다. xml파일은 바뀌지 않고, MainActivity.java와 VpAdapter.java 는 바뀌었습니다.

 

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  xmlns:tools="http://schemas.android.com/tools"
  android:id="@+id/main"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  tools:context=".MainActivity">

  <TextView
    android:id="@+id/tv_order"
    android:layout_width="50dp"
    android:layout_height="wrap_content"
    android:layout_marginEnd="5dp"
    android:layout_marginBottom="5dp"
    android:background="#88000000"
    android:gravity="center"
    android:textColor="@color/white"
    app:layout_constraintBottom_toBottomOf="@id/view_pager2"
    app:layout_constraintEnd_toEndOf="parent" />

  <androidx.viewpager2.widget.ViewPager2
    android:id="@+id/view_pager2"
    android:layout_width="match_parent"
    android:layout_height="300dp"
    android:layout_gravity="center"
    android:layout_marginTop="100dp"

    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintHorizontal_bias="0.0"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"/>


</androidx.constraintlayout.widget.ConstraintLayout>

 

 

vp_item.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <ImageView
    android:id="@+id/images"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scaleType="fitCenter" />

</FrameLayout>

 

MainActivity.java

public class MainActivity extends AppCompatActivity {

  // 이미지 뷰페이저
  private VpAdapter vpAdapter;
  private ViewPager2 viewPager2;
  private TextView tv_order;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    viewPager2 = findViewById(R.id.view_pager2);
    tv_order = findViewById(R.id.tv_order);


    List<String> images = new ArrayList<>();

    // 더미 사진을 이용 했습니다. 각자 원하는 사진을 추가 해주시면 됩니다.
    images.add(String.valueOf(R.drawable.cat1));
    images.add(String.valueOf(R.drawable.cat2));
    images.add(String.valueOf(R.drawable.cat3));
    images.add(String.valueOf(R.drawable.cat4));
    images.add(String.valueOf(R.drawable.cat5));

    vpAdapter = new VpAdapter(MainActivity.this ,images);
    viewPager2.setAdapter(vpAdapter);

    tv_order.setText("1 / " + images.size());

    int middle = Integer.MAX_VALUE / 2;
    int extra = middle % images.size();
    viewPager2.setCurrentItem(middle - extra, false);

    viewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
      @Override
      public void onPageSelected(int position) {
        super.onPageSelected(position);
        int realPosition = position % images.size();
        String order = (realPosition + 1) + " / " + images.size();
        tv_order.setText(order);
      }
    });

  }
}

 

VpAdapter.java

public class VpAdapter extends RecyclerView.Adapter<VpAdapter.ViewHolder> {

  private Context context;
  private List<String> items;

  public VpAdapter(Context context, List<String> items) {
    this.context = context;
    this.items = items;
  }

  @NonNull
  @Override
  public VpAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    View view = LayoutInflater.from(context).inflate(R.layout.vp_item, parent, false);
    return new ViewHolder(view);
  }

  @Override
  public void onBindViewHolder(@NonNull VpAdapter.ViewHolder holder, int position) {
      int realPosition = position % items.size();
      holder.bind(items.get(realPosition));
  }

  @Override
  public int getItemCount() {
    if (null != items && !items.isEmpty()) {
      if (1 >= items.size()) {
        return 1;
      } else {
        return Integer.MAX_VALUE;
      }
    } else {
      return 1;
    }
  }

  public class ViewHolder extends RecyclerView.ViewHolder {
    ImageView imageView;
    public ViewHolder(@NonNull View itemView) {
      super(itemView);
      imageView = itemView.findViewById(R.id.images);
    }

    public void bind(String imageResource) {
      imageView.setImageResource(Integer.parseInt(imageResource));
    }
  }
}

 

 

 

 

우선 VpAdapter.java 의 

 

@Override
public void onBindViewHolder(@NonNull VpAdapter.ViewHolder holder, int position) {
    int realPosition = position % items.size();
    holder.bind(items.get(realPosition));
}

 

 

이부분을 살펴보도록 하겠습니다.

 

int realPosition = position % items.size() 를 사용하여 실제 데이터를 가져올 위치를 계산합니다.

 

예를 들어, items에 5장의 사진이 들어있다고 가정해보도록 하겠습니다.

그러면 position 값이 0~4까지는 realPosition에는 그대로 들어가게 됩니다.

 

만약 position이 5가 되면, ' 5 % 5 = 0 ' 이 되어서, realPosition 이 0이 되어 다시 첫번째 사진을 가리키게 됩니다.

 

이런 방식으로, position이 아무리 증가해도 realPosition은 실제 사진의 0에서 4까지의 값을 가지게 되어, 무한 스크롤이 구현됩니다.

 

 

 

그 다음으로


@Override
public int getItemCount() {
  if (null != items && !items.isEmpty()) {
    if (1 >= items.size()) {
      return 1;
    } else {
      return Integer.MAX_VALUE;
    }
  } else {
    return 1;
  }
}

 

 

어댑터가 관리하는 아이템의 총 갯수를 살펴보도록 하겠습니다.

 

아이템의 크기가 1보다 큰 경우, Integer.MAX_VALUE 를 반환하도록 했습니다. 아이템의 갯수가 int의 최대값 약 21억의 숫자를 반환하도록 하여, 사실상 무한에 가까운 스크롤을 구현합니다.

 

 

 

다음으로는, MainActivity.java 를 살펴보도록 하겠습니다.

 

int middle = Integer.MAX_VALUE / 2;
int extra = middle % images.size();
viewPager2.setCurrentItem(middle - extra, false);

 

이부분을 살펴보면,

middle 값 계산 : Integer.MAX_VALUE / 2 를 계산하여, 중간 위치를 찾습니다. 이 값은 뷰페이저가 처음 시작할 위치를 설정하는데 사용됩니다.

 

extra 값 계산 : middle 값을 images.size() 로 나눈 나머지값을 계산하여, 중간위치가 실제 이미지 리스트의 시작 위치와 일치하도록 조정하는데 사용됩니다.

 

middle 에서 extra를 빼서, 뷰페이저의 현재 아이템을 중간 위치에서 시작되도록 설정합니다.

 

 

이후에, 

viewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
  @Override
  public void onPageSelected(int position) {
    super.onPageSelected(position);
    int realPosition = position % images.size();
    String order = (realPosition + 1) + " / " + images.size();
    tv_order.setText(order);
  }
});

 

뷰페이저의 위치를 나타내는 TextView를 설정해주면 끝입니다.

 

뷰페이저2는 내부적으로 리사이클러뷰를 기반으로 구현되어 있습니다.

따라서 뷰 홀더 패턴을 사용하여 뷰를 재사용하기 때문에,
Integer.MAX_VALUE 크기의 아이템을 사용해도 실제 메모리 낭비는 발생하지 않는다는 점을 참고하면 좋을것 같습니다.

 

 

 

작동 화면