본문 바로가기

[Android/Java] 안드로이드 ViewPage2 무한 이미지 슬라이딩 구현하기

꿈꾸는블로그왕 2021. 1. 15.

 

오늘은 ViewPager2를 이용해서 무한 이미지 슬라이딩을 구현해보도록 하겠습니다.

 

완성된 모습은 아래와 같습니다.

 

 

 

Step01. build.gradle 추가하기

ViewPager2는 material Library에 추가되어 있으므로 material 라이브러리가 추가 되어 있어야합니다.

추가적으로 이미지로딩을 위해 Glide Library를 추가해줍니다.

implementation 'com.google.android.material:material:1.2.1'

// Glide
implementation 'com.github.bumptech.glide:glide:4.11.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'

 

Step02. 기본 화면 구성하기

[activity_main.xml]

MainActivity 화면에 ViewPager2를 넣습니다.

<?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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/vp_image_slider"
        android:layout_width="match_parent"
        android:layout_height="400dp"
        android:paddingStart="80dp"
        android:paddingEnd="80dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

 

[slide_item.xml]

ViewPager2 안에 들어갈 이미지는 Adpater를 통해 구현할 예정이며, 이때 들어갈 View를 만들어 줍니다.

<?xml version="1.0" encoding="utf-8"?>
<ImageView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/image_slider"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scaleType="centerCrop"
    android:contentDescription="@string/app_name" />

 

[SliderAdapter.java]

이미지에 대한 정보는 Adapter 생성시 액티비티에서 넘기고, sliderItems 리스트에 담을 예정입니다.

Glide Library를 통해서 ImageView에 해당 이미지를 로딩합니다.

Try Catch 구문을 통해서 에러시 기본 이미지를 로딩할 수 도 있습니다.

public class SliderAdapter extends RecyclerView.Adapter<SliderAdapter.SliderViewHolder> {

    private static final String TAG = "SliderAdapter";

    private Context mContext;
    private List<String> sliderItems;

    public SliderAdapter(Context context, List<String> sliderImage) {
        mContext = context;
        this.sliderItems = sliderImage;
    }

    @NonNull
    @Override
    public SliderViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new SliderViewHolder(SlideItemBinding.inflate(LayoutInflater.from(parent.getContext()),
                parent,
                false));
    }

    @Override
    public void onBindViewHolder(@NonNull SliderViewHolder holder, int position) {
        holder.bind(sliderItems.get(position));
    }

    @Override
    public int getItemCount() {
        return sliderItems.size();
    }

    class SliderViewHolder extends RecyclerView.ViewHolder {

        private SlideItemBinding mBinding;

        public SliderViewHolder(SlideItemBinding binding) {
            super(binding.getRoot());
            mBinding = binding;
        }

        void bind(String sliderItem) {
            try {
                Glide.with(mContext).load(sliderItem).into(mBinding.imageSlider);
            } catch (Exception e) {
                Log.d(TAG, "ERROR: " + e.getMessage());
            }
        }
    }

}

 

[MainActivity.java]

public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding mBinding;

    private Handler sliderHandler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mBinding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(mBinding.getRoot());

        List<String> sliderItems = new ArrayList<>();
        sliderItems.add("https://cdn.pixabay.com/photo/2019/12/26/10/44/horse-4720178_1280.jpg");
        sliderItems.add("https://cdn.pixabay.com/photo/2020/11/04/15/29/coffee-beans-5712780_1280.jpg");
        sliderItems.add("https://cdn.pixabay.com/photo/2020/11/10/01/34/pet-5728249_1280.jpg");
        sliderItems.add("https://cdn.pixabay.com/photo/2020/12/21/19/05/window-5850628_1280.png");
        sliderItems.add("https://cdn.pixabay.com/photo/2014/03/03/16/15/mosque-279015_1280.jpg");
        sliderItems.add("https://cdn.pixabay.com/photo/2019/10/15/13/33/red-deer-4551678_1280.jpg");

        mBinding.vpImageSlider.setAdapter(new SliderAdapter(this, mBinding.vpImageSlider, sliderItems));
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mBinding = null;
    }

}

 

여기까지 적용했을 때 작동되는 모습입니다.

 

 

Step03. 양쪽 미리보기 구현하기

양쪽 페이지를 모두 미리 보기 상태로 만드려면 setClipToPadding(false), setClipChildren(false)를 적용합니다.

또한 setOffscreenPageLimit(3)으로 설정합니다. 이 함수는 현재 보여지는 화면에서 몇개의 페이지를 유지할 지를 지정합니다. 3으로 설정하여 현재 페이지와 두개의 페이지를 가지고 있다고 생각하시면 됩니다.

아래와 같이 CompositePageTransformer를 사용하여 Viewpager의 좌우 프리뷰를 구현합니다.

mBinding.vpImageSlider.setClipToPadding(false);
mBinding.vpImageSlider.setClipChildren(false);
mBinding.vpImageSlider.setOffscreenPageLimit(3);
mBinding.vpImageSlider.getChildAt(0).setOverScrollMode(RecyclerView.OVER_SCROLL_NEVER);

CompositePageTransformer compositePageTransformer = new CompositePageTransformer();
compositePageTransformer.addTransformer(new MarginPageTransformer(40));
compositePageTransformer.addTransformer(new ViewPager2.PageTransformer() {
      @Override
      public void transformPage(@NonNull View page, float position) {
           float r = 1 - Math.abs(position);
           page.setScaleY(0.85f + r * 0.15f);
      }
});

mBinding.vpImageSlider.setPageTransformer(compositePageTransformer);

 

 

위 코드를 적용한 모습입니다.

 

 

Step04. 자동 스크롤 적용합니다.

MainActivity 파일에 페이지를 변경하는 작업을 나타내는 Runnable을 작성합니다.

    private Runnable sliderRunnable = new Runnable() {
        @Override
        public void run() {
            mBinding.vpImageSlider.setCurrentItem(mBinding.vpImageSlider.getCurrentItem() + 1);
        }
    };

OnPageChangeCallback를 사용하여 페이지가 선택되었을때 페이지를 변경할 수 있도록 Handler를 작동시켜 줍니다. 

2초후마다 페이지가 변경되도록 설정했습니다.

// 최상위 핸들러 정의
private Handler sliderHandler = new Handler();

        ...
        //onCreate 안에
        mBinding.vpImageSlider.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
            @Override
            public void onPageSelected(int position) {
                super.onPageSelected(position);
                sliderHandler.removeCallbacks(sliderRunnable);
                sliderHandler.postDelayed(sliderRunnable, 2000);
            }
        });

 

액티비티가 일시정지일때 Handler를 정지시켜주고, 액티비티가 재시작될 때 다시 실행시켜줍니다.

@Override
protected void onPause() {
    super.onPause();
    sliderHandler.removeCallbacks(sliderRunnable);
}

@Override
protected void onResume() {
    super.onResume();
    sliderHandler.postDelayed(sliderRunnable, 2000);
}

 

Adapter에서 작업처리를 위해 ViewPager2 객체를 파라미터로 넘겨줍니다.

mBinding.vpImageSlider.setAdapter(new SliderAdapter(this, mBinding.vpImageSlider, sliderItems));

 

Adapter에서는 리스트 끝에 올때 새로운 리스트를 추가 하는 코드를 추가해 줍니다.

@Override
public void onBindViewHolder(@NonNull SliderViewHolder holder, int position) {
    holder.bind(sliderItems.get(position));
    if (position == sliderItems.size() - 2) {
        mViewPager2.post(runnable);
    }
}


private Runnable runnable = new Runnable() {
    @Override
    public void run() {
        sliderItems.addAll(sliderItems);
        notifyDataSetChanged();
    }
};

무한 슬라이딩이 적용된 모습니다.

 

 

 

전체코드는 아래와 같습니다.

 

[MainActivity.java] 전체코드

public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding mBinding;

    private Handler sliderHandler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mBinding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(mBinding.getRoot());

        List<String> sliderItems = new ArrayList<>();
        sliderItems.add("https://cdn.pixabay.com/photo/2019/12/26/10/44/horse-4720178_1280.jpg");
        sliderItems.add("https://cdn.pixabay.com/photo/2020/11/04/15/29/coffee-beans-5712780_1280.jpg");
        sliderItems.add("https://cdn.pixabay.com/photo/2020/11/10/01/34/pet-5728249_1280.jpg");
        sliderItems.add("https://cdn.pixabay.com/photo/2020/12/21/19/05/window-5850628_1280.png");
        sliderItems.add("https://cdn.pixabay.com/photo/2014/03/03/16/15/mosque-279015_1280.jpg");
        sliderItems.add("https://cdn.pixabay.com/photo/2019/10/15/13/33/red-deer-4551678_1280.jpg");

        mBinding.vpImageSlider.setAdapter(new SliderAdapter(this, mBinding.vpImageSlider, sliderItems));

        mBinding.vpImageSlider.setClipToPadding(false);
        mBinding.vpImageSlider.setClipChildren(false);
        mBinding.vpImageSlider.setOffscreenPageLimit(3);
        mBinding.vpImageSlider.getChildAt(0).setOverScrollMode(RecyclerView.OVER_SCROLL_NEVER);

        CompositePageTransformer compositePageTransformer = new CompositePageTransformer();
        compositePageTransformer.addTransformer(new MarginPageTransformer(40));
        compositePageTransformer.addTransformer(new ViewPager2.PageTransformer() {
            @Override
            public void transformPage(@NonNull View page, float position) {
                float r = 1 - Math.abs(position);
                page.setScaleY(0.85f + r * 0.15f);
            }
        });

        mBinding.vpImageSlider.setPageTransformer(compositePageTransformer);

        mBinding.vpImageSlider.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
            @Override
            public void onPageSelected(int position) {
                super.onPageSelected(position);
                sliderHandler.removeCallbacks(sliderRunnable);
                sliderHandler.postDelayed(sliderRunnable, 2000);
            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mBinding = null;
    }

    private Runnable sliderRunnable = new Runnable() {
        @Override
        public void run() {
            mBinding.vpImageSlider.setCurrentItem(mBinding.vpImageSlider.getCurrentItem() + 1);
        }
    };

    @Override
    protected void onPause() {
        super.onPause();
        sliderHandler.removeCallbacks(sliderRunnable);
    }

    @Override
    protected void onResume() {
        super.onResume();
        sliderHandler.postDelayed(sliderRunnable, 2000);
    }
}

 

[SliderAdapter.java] 전체코드

public class SliderAdapter extends RecyclerView.Adapter<SliderAdapter.SliderViewHolder> {

    private static final String TAG = "SliderAdapter";

    private Context mContext;
    private ViewPager2 mViewPager2;
    private List<String> sliderItems;

    public SliderAdapter(Context context, ViewPager2 viewPager2, List<String> sliderImage) {
        mContext = context;
        mViewPager2 = viewPager2;
        this.sliderItems = sliderImage;
    }

    @NonNull
    @Override
    public SliderViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new SliderViewHolder(SlideItemBinding.inflate(LayoutInflater.from(parent.getContext()),
                parent,
                false));
    }

    @Override
    public void onBindViewHolder(@NonNull SliderViewHolder holder, int position) {
        holder.bind(sliderItems.get(position));
        if (position == sliderItems.size() - 2) {
            mViewPager2.post(runnable);
        }
    }

    @Override
    public int getItemCount() {
        return sliderItems.size();
    }

    class SliderViewHolder extends RecyclerView.ViewHolder {

        private SlideItemBinding mBinding;

        public SliderViewHolder(SlideItemBinding binding) {
            super(binding.getRoot());
            mBinding = binding;
        }

        void bind(String sliderItem) {
            try {
                Glide.with(mContext).load(sliderItem).into(mBinding.imageSlider);
            } catch (Exception e) {
                Log.d(TAG, "ERROR: " + e.getMessage());
            }
        }
    }

    private Runnable runnable = new Runnable() {
        @Override
        public void run() {
            sliderItems.addAll(sliderItems);
            notifyDataSetChanged();
        }
    };

}

댓글