ViewPager2
는 ViewPager
라이브러리의 개선된 버전으로, 향상된 기능을 제공하고 ViewPager
사용과 관련된 일반적인 문제를 해결합니다.
앱에서 이미 ViewPager
를 사용하고 있다면 이 페이지를 참고하여 ViewPager2
로 마이그레이션하는 방법을 자세히 알아보세요.
앱에서 ViewPager2
를 사용하려고 하는데 현재 ViewPager
를 사용하고 있지 않다면 ViewPager2로 프래그먼트 간 슬라이드 및 ViewPager2를 사용하여 탭으로 스와이프 뷰 만들기에서 자세한 내용을 확인하세요.
ViewPager2로 이전하여 얻을 수 있는 이점
이전하는 주된 이유는 ViewPager2
는 적극적인 개발 지원을 받고 있지만 ViewPager
는 받지 않기 때문입니다. 또한 ViewPager2
에는 몇 가지 구체적인 이점도 있습니다.
수직 방향 지원
ViewPager2
는 기존 가로 페이징 외에도 세로 페이징을 지원합니다. android:orientation
속성을 설정하여 ViewPager2
요소에 세로 페이징을 사용 설정할 수 있습니다.
<androidx.viewpager2.widget.ViewPager2
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/pager"
android:orientation="vertical" />
setOrientation() 메서드를 사용하여 프로그래매틱 방식으로 이 속성을 설정할 수도 있습니다.
오른쪽에서 왼쪽 지원
ViewPager2
는 오른쪽에서 왼쪽(RTL) 페이징을 지원합니다. RTL 페이징은 언어에 따라 필요한 경우 자동으로 사용 설정되지만 android:layoutDirection
속성을 설정하여 ViewPager2
요소의 RTL 페이징을 수동으로 사용 설정할 수도 있습니다.
<androidx.viewpager2.widget.ViewPager2
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/pager"
android:layoutDirection="rtl" />
setLayoutDirection() 메서드를 사용하여 프로그래매틱 방식으로 이 속성을 설정할 수도 있습니다.
수정 가능한 프래그먼트 컬렉션
ViewPager2
는 수정 가능한 프래그먼트 컬렉션을 통해 페이징을 지원하며 기본 컬렉션이 변경될 때 notifyDatasetChanged()
를 호출하여 UI를 업데이트합니다.
즉, 앱은 런타임에 프래그먼트 컬렉션을 동적으로 수정할 수 있으며 ViewPager2
는 수정된 컬렉션을 올바르게 표시합니다.
DiffUtil
ViewPager2
는 RecyclerView
를 기반으로 빌드되므로 DiffUtil
유틸리티 클래스에 액세스할 수 있습니다. 이렇게 하면 여러 이점이 있지만 특히 ViewPager2
객체는 기본적으로 RecyclerView
클래스의 데이터 세트 변경 애니메이션을 활용합니다.
ViewPager2로 앱 이전
앱의 ViewPager
객체를 ViewPager2
로 업데이트하려면 다음 단계를 따르세요.
XML 레이아웃 파일 업데이트
먼저 XML 레이아웃 파일의 ViewPager
요소를 ViewPager2
요소로 바꿉니다.
<!-- A ViewPager element -->
<android.support.v4.view.ViewPager
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- A ViewPager2 element -->
<androidx.viewpager2.widget.ViewPager2
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
어댑터 클래스 업데이트
ViewPager
를 사용할 때는 객체에 새 페이지를 제공하는
어댑터 클래스를 확장해야 했습니다. ViewPager
는 사용 사례에 따라 세 가지 추상 클래스를 사용했습니다. ViewPager2
는 두 가지 추상 클래스만 사용합니다.
ViewPager2
객체로 변환하는 ViewPager
객체마다 다음과 같이 어댑터 클래스를 업데이트하여 적절한 추상 클래스를 확장합니다.
ViewPager
가PagerAdapter
를 사용하여 뷰를 통해 페이징하는 경우ViewPager2
와 함께RecyclerView.Adapter
를 사용합니다.ViewPager
가FragmentPagerAdapter
를 사용하여 고정된 소수의 프래그먼트를 통해 페이징하는 경우ViewPager2
와 함께FragmentStateAdapter
를 사용합니다.ViewPager
가FragmentStatePagerAdapter
를 사용하여 많은 수의 프래그먼트를 통해 페이징하는 경우ViewPager2
와 함께FragmentStateAdapter
를 사용합니다.
생성자 매개변수
FragmentPagerAdapter
또는 FragmentStatePagerAdapter
에서 상속되는 프래그먼트 기반 어댑터 클래스는 항상 단일 FragmentManager
객체를 생성자 매개변수로 허용합니다. ViewPager2
어댑터 클래스의 FragmentStateAdapter
를 확장하면 생성자 매개변수에 다음 옵션을 대신 사용할 수 있습니다.
ViewPager2
객체가 있는FragmentActivity
객체 또는Fragment
객체 대부분의 경우 이 옵션을 사용하는 것이 더 좋습니다.FragmentManager
객체 및Lifecycle
객체.
RecyclerView.Adapter
에서 직접 상속되는 뷰 기반 어댑터 클래스에는 생성자 매개변수가 필요하지 않습니다.
메서드 재정의
어댑터 클래스에서 재정의해야 하는 ViewPager2
의 메서드도 ViewPager
의 메서드와 다릅니다.
getCount()
가 아닌getItemCount()
를 재정의합니다. 이 메서드는 이름 외에는 변경되지 않습니다.- 프래그먼트 기반 어댑터 클래스에서
getItem()
대신createFragment()
를 재정의합니다. 새createFragment()
메서드가 함수가 호출될 때마다 인스턴스를 재사용하는 대신 항상 새 프래그먼트 인스턴스를 제공하도록 해야 합니다.
요약
요약하면, ViewPager2
와 함께 사용할 ViewPager
어댑터 클래스를 변환하려면 다음과 같이 변경해야 합니다.
- 슈퍼클래스를 뷰를 통해 페이징하는 경우
RecyclerView.Adapter
로 변경하고 프래그먼트를 통해 페이징하는 경우FragmentStateAdapter
로 변경합니다. - 프래그먼트 기반 어댑터 클래스에서는 생성자 매개변수를 변경합니다.
getCount()
가 아닌getItemCount()
를 재정의합니다.- 프래그먼트 기반 어댑터 클래스에서
getItem()
대신createFragment()
를 재정의합니다.
Kotlin
// A simple ViewPager adapter class for paging through fragments class ScreenSlidePagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) { override fun getCount(): Int = NUM_PAGES override fun getItem(position: Int): Fragment = ScreenSlidePageFragment() } // An equivalent ViewPager2 adapter class class ScreenSlidePagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) { override fun getItemCount(): Int = NUM_PAGES override fun createFragment(position: Int): Fragment = ScreenSlidePageFragment() }
Java
// A simple ViewPager adapter class for paging through fragments public class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter { public ScreenSlidePagerAdapter(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int position) { return new ScreenSlidePageFragment(); } @Override public int getCount() { return NUM_PAGES; } } // An equivalent ViewPager2 adapter class private class ScreenSlidePagerAdapter extends FragmentStateAdapter { public ScreenSlidePagerAdapter(FragmentActivity fa) { super(fa); } @Override public Fragment createFragment(int position) { return new ScreenSlidePageFragment(); } @Override public int getItemCount() { return NUM_PAGES; } }
TabLayout 인터페이스 리팩터링
ViewPager2
에서는 TabLayout
통합이 변경되었습니다. 현재 ViewPager
를 TabLayout
객체와 함께 사용하여 탐색용 가로 탭을 표시하고 있다면 ViewPager2
와 통합하기 위해 TabLayout
객체를 리팩터링해야 합니다.
TabLayout
는 ViewPager2
에서 분리되었으며 이제 Material 구성요소의 일부로 사용할 수 있습니다. 즉, build.gradle
파일에 적절한 종속 항목을 추가해야 사용할 수 있습니다.
Groovy
implementation "com.google.android.material:material:1.1.0-beta01"
Kotlin
implementation("com.google.android.material:material:1.1.0-beta01")
XML 레이아웃 파일의 계층 구조에서 TabLayout
요소의 위치도 변경해야 합니다. ViewPager
의 경우 TabLayout
요소가 ViewPager
요소의 하위 요소로 선언되지만 ViewPager2
의 경우 TabLayout
요소는 동일한 수준에서 ViewPager2
요소 바로 위에 선언됩니다.
<!-- A ViewPager element with a TabLayout -->
<androidx.viewpager.widget.ViewPager
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</androidx.viewpager.widget.ViewPager>
<!-- A ViewPager2 element with a TabLayout -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
마지막으로 TabLayout
객체를 ViewPager
객체에 연결하는 코드를 업데이트해야 합니다. TabLayout
는 자체 setupWithViewPager()
메서드를 사용하여 ViewPager
와 통합하지만 ViewPager2
와 통합하려면 TabLayoutMediator
인스턴스가 필요합니다.
TabLayoutMediator
객체는 TabLayout
객체의 페이지 제목을 생성하는 작업도 처리합니다. 즉, 어댑터 클래스에서 getPageTitle()
를 재정의할 필요가 없습니다.
Kotlin
// Integrating TabLayout with ViewPager class CollectionDemoFragment : Fragment() { ... override fun onViewCreated(view: View, savedInstanceState: Bundle?) { val tabLayout = view.findViewById(R.id.tab_layout) tabLayout.setupWithViewPager(viewPager) } ... } class DemoCollectionPagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) { override fun getCount(): Int = 4 override fun getPageTitle(position: Int): CharSequence { return "OBJECT ${(position + 1)}" } ... } // Integrating TabLayout with ViewPager2 class CollectionDemoFragment : Fragment() { ... override fun onViewCreated(view: View, savedInstanceState: Bundle?) { val tabLayout = view.findViewById(R.id.tab_layout) TabLayoutMediator(tabLayout, viewPager) { tab, position -> tab.text = "OBJECT ${(position + 1)}" }.attach() } ... }
Java
// Integrating TabLayout with ViewPager public class CollectionDemoFragment extends Fragment { ... @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { TabLayout tabLayout = view.findViewById(R.id.tab_layout); tabLayout.setupWithViewPager(viewPager); } ... } public class DemoCollectionPagerAdapter extends FragmentStatePagerAdapter { ... @Override public int getCount() { return 4; } @Override public CharSequence getPageTitle(int position) { return "OBJECT " + (position + 1); } ... } // Integrating TabLayout with ViewPager2 public class CollectionDemoFragment : Fragment() { ... @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { TabLayout tabLayout = view.findViewById(R.id.tab_layout); new TabLayoutMediator(tabLayout, viewPager, (tab, position) -> tab.setText("OBJECT " + (position + 1)) ).attach(); } ... }
중첩된 스크롤 가능 요소 지원
ViewPager2
는 스크롤 뷰가 포함된 ViewPager2
객체와 방향이 같은 경우 기본적으로 중첩된 스크롤 뷰를 지원하지 않습니다. 예를 들어 세로 방향 ViewPager2
객체 내의 세로 스크롤 뷰에서는 스크롤이 작동하지 않습니다.
방향이 같은 ViewPager2
객체 내에서 스크롤 뷰를 지원하려면 중첩된 요소를 스크롤할 것으로 예상될 때 ViewPager2
객체에서 requestDisallowInterceptTouchEvent()
를 호출해야 합니다. ViewPager2 중첩 스크롤 샘플에서는 다목적 맞춤 래퍼 레이아웃을 사용하여 이 문제를 해결하는 한 가지 방법을 보여줍니다.
추가 리소스
ViewPager2
에 관해 자세히 알아보려면 다음 추가 리소스를 참조하세요.
샘플
- GitHub의 ViewPager2 샘플
동영상
- 페이지 넘기기: ViewPager2로 이전(Android Dev Summit '19)