ViewPager2로 프래그먼트 간 슬라이드

Compose 방식 사용해 보기
Jetpack Compose는 Android의 권장 UI 도구 키트입니다. Compose에서 Pager를 사용하는 방법을 알아보세요.

화면 슬라이드는 하나의 전체 화면에서 다른 전체 화면으로 전환하는 것으로, 설정 마법사 또는 슬라이드쇼와 같은 UI에서 일반적으로 사용됩니다. 이 주제에서는 ViewPager2 객체로 화면을 슬라이드하는 방법을 보여줍니다. ViewPager2 객체는 화면 슬라이드에 자동으로 애니메이션을 적용할 수 있습니다. 다음은 하나의 콘텐츠 화면에서 다음 화면으로 전환하는 화면 슬라이드의 예입니다.

그림 1. 화면 슬라이드 애니메이션
 

이를 건너뛰고 실제로 완벽히 작동하는 예를 보려면, GitHub에서 이 샘플 앱을 확인하세요.

ViewPager2를 사용하려면 프로젝트에 AndroidX 종속 항목을 추가해야 합니다. 그런 다음 다음 섹션에 설명된 단계를 따르세요.

뷰 만들기

나중에 프래그먼트 콘텐츠에 사용할 레이아웃 파일을 만듭니다. 프래그먼트 콘텐츠에 대한 문자열도 정의해야 합니다. 다음 예제에는 텍스트를 표시하는 텍스트 뷰가 포함되어 있습니다.

<!-- fragment_screen_slide_page.xml -->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/content"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <TextView style="?android:textAppearanceMedium"
        android:padding="16dp"
        android:lineSpacingMultiplier="1.2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/lorem_ipsum" />
</ScrollView>

프래그먼트 만들기

onCreateView() 메서드에서 만든 레이아웃을 반환하는 Fragment 클래스를 만듭니다. 그러면 사용자에게 표시할 새 페이지가 필요할 때마다 상위 활동에서 이 프래그먼트의 인스턴스를 만들 수 있습니다.

Kotlin

import androidx.fragment.app.Fragment

class ScreenSlidePageFragment : Fragment() {

    override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
    ): View = inflater.inflate(R.layout.fragment_screen_slide_page, container, false)
}

자바

import androidx.fragment.app.Fragment;
...
public class ScreenSlidePageFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        return (ViewGroup) inflater.inflate(
                R.layout.fragment_screen_slide_page, container, false);
    }
}

ViewPager2 추가

ViewPager2 객체에는 페이지 간 전환을 위한 스와이프 동작이 내장되어 있으며 기본적으로 화면 슬라이드 애니메이션을 표시하므로 직접 애니메이션을 만들 필요가 없습니다. ViewPager2는 표시할 새 페이지의 요소로 FragmentStateAdapter 객체를 사용하므로 FragmentStateAdapter는 개발자가 만든 프래그먼트 클래스를 사용합니다.

시작하려면 ViewPager2 객체가 포함된 레이아웃을 만듭니다.

<!-- activity_screen_slide.xml -->
<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" />

다음 작업을 실행하는 활동을 만듭니다.

  • 콘텐츠 뷰를 ViewPager2가 있는 레이아웃으로 설정합니다.
  • FragmentStateAdapter 추상 클래스를 확장하는 클래스를 만들고 createFragment() 메서드를 구현하여 ScreenSlidePageFragment의 인스턴스를 새 페이지로 제공합니다. 어댑터에서 만드는 페이지 수를 반환하는 페이저 어댑터의 getItemCount() 메서드를 구현해야 합니다. 예에서는 5개입니다.
  • FragmentStateAdapterViewPager2 객체에 연결합니다.

Kotlin

import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
...
/**
 * The number of pages (wizard steps) to show in this demo.
 */
private const val NUM_PAGES = 5

class ScreenSlidePagerActivity : FragmentActivity() {

    /**
     * The pager widget, which handles animation and allows swiping horizontally
     * to access previous and next wizard steps.
     */
    private lateinit var viewPager: ViewPager2

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_screen_slide)

        // Instantiate a ViewPager2 and a PagerAdapter.
        viewPager = findViewById(R.id.pager)

        // The pager adapter, which provides the pages to the view pager widget.
        val pagerAdapter = ScreenSlidePagerAdapter(this)
        viewPager.adapter = pagerAdapter
    }

    override fun onBackPressed() {
        if (viewPager.currentItem == 0) {
            // If the user is currently looking at the first step, allow the system to handle
            // the Back button. This calls finish() on this activity and pops the back stack.
            super.onBackPressed()
        } else {
            // Otherwise, select the previous step.
            viewPager.currentItem = viewPager.currentItem - 1
        }
    }

    /**
     * A simple pager adapter that represents 5 ScreenSlidePageFragment objects, in
     * sequence.
     */
    private inner class ScreenSlidePagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) {
        override fun getItemCount(): Int = NUM_PAGES

        override fun createFragment(position: Int): Fragment = ScreenSlidePageFragment()
    }
}

자바

import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
...
public class ScreenSlidePagerActivity extends FragmentActivity {
    /**
     * The number of pages (wizard steps) to show in this demo.
     */
    private static final int NUM_PAGES = 5;

    /**
     * The pager widget, which handles animation and allows swiping horizontally to access previous
     * and next wizard steps.
     */
    private ViewPager2 viewPager;

    /**
     * The pager adapter, which provides the pages to the view pager widget.
     */
    private FragmentStateAdapter pagerAdapter;

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

        // Instantiate a ViewPager2 and a PagerAdapter.
        viewPager = findViewById(R.id.pager);
        pagerAdapter = new ScreenSlidePagerAdapter(this);
        viewPager.setAdapter(pagerAdapter);
    }

    @Override
    public void onBackPressed() {
        if (viewPager.getCurrentItem() == 0) {
            // If the user is currently looking at the first step, allow the system to handle the
            // Back button. This calls finish() on this activity and pops the back stack.
            super.onBackPressed();
        } else {
            // Otherwise, select the previous step.
            viewPager.setCurrentItem(viewPager.getCurrentItem() - 1);
        }
    }

    /**
     * A simple pager adapter that represents 5 ScreenSlidePageFragment objects, in
     * sequence.
     */
    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;
        }
    }
}

PageTransformer를 사용하여 애니메이션 맞춤설정

기본 화면 슬라이드 애니메이션과 다른 애니메이션을 표시하려면 ViewPager2.PageTransformer 인터페이스를 구현하고 ViewPager2 객체에 제공합니다. 인터페이스는 단일 메서드인 transformPage()를 노출합니다. 화면 전환의 각 지점에서 이 메서드는 표시되는 페이지(일반적으로 단 한 개의 페이지만 표시됨)마다, 그리고 화면에 표시되지 않는 인접 페이지에 대해 한 번씩 호출됩니다. 예를 들어, 페이지 3이 표시되고 사용자가 페이지 4로 드래그하면 동작의 각 단계에서 페이지 2, 3, 4에 대해 transformPage()가 호출됩니다.

그런 다음 transformPage() 구현에서 화면의 페이지 위치를 기반으로 변환해야 하는 페이지를 결정하여 맞춤 슬라이드 애니메이션을 만들 수 있습니다. transformPage() 메서드의 position 매개변수에서 페이지 위치를 가져옵니다.

position 매개변수는 화면 중앙을 기준으로 지정된 페이지가 있는 위치를 나타냅니다. 이 매개변수는 사용자가 일련의 페이지를 스크롤할 때 변경되는 동적 속성입니다. 페이지가 화면을 채우면 위치 값은 0입니다. 페이지가 화면 오른쪽에서 벗어나면 위치 값은 1입니다. 사용자가 페이지 1과 페이지 2의 중간으로 스크롤하면 페이지 1의 위치 값은 -0.5이고 페이지 2의 위치 값은 0.5입니다. 화면의 페이지 위치를 기반으로 setAlpha(), setTranslationX() 또는 setScaleY()와 같은 메서드로 페이지 속성을 설정하여 맞춤 슬라이드 애니메이션을 만들 수 있습니다.

구현이 있으면 구현으로 호출하여 맞춤 애니메이션을 적용합니다.PageTransformersetPageTransformer() 예를 들어 PageTransformer라는 ZoomOutPageTransformer가 있는 경우 다음과 같이 맞춤 애니메이션을 설정할 수 있습니다.

Kotlin

val viewPager: ViewPager2 = findViewById(R.id.pager)
...
viewPager.setPageTransformer(ZoomOutPageTransformer())

자바

ViewPager2 viewPager = findViewById(R.id.pager);
...
viewPager.setPageTransformer(new ZoomOutPageTransformer());

페이지 축소 변환기심도 페이지 변환기 섹션에서 PageTransformer의 예를 참고하세요.

페이지 축소 변환기

이 페이지 변환기는 인접 페이지 사이를 스크롤하면 페이지가 축소되면서 페이드아웃됩니다. 페이지가 중앙에 가까워지면 원래 크기로 다시 커지면서 페이드인됩니다.

그림 2. ZoomOutPageTransformer
 

Kotlin

private const val MIN_SCALE = 0.85f
private const val MIN_ALPHA = 0.5f

class ZoomOutPageTransformer : ViewPager2.PageTransformer {

    override fun transformPage(view: View, position: Float) {
        view.apply {
            val pageWidth = width
            val pageHeight = height
            when {
                position < -1 -> { // [-Infinity,-1)
                    // This page is way off-screen to the left.
                    alpha = 0f
                }
                position <= 1 -> { // [-1,1]
                    // Modify the default slide transition to shrink the page as well.
                    val scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position))
                    val vertMargin = pageHeight * (1 - scaleFactor) / 2
                    val horzMargin = pageWidth * (1 - scaleFactor) / 2
                    translationX = if (position < 0) {
                        horzMargin - vertMargin / 2
                    } else {
                        horzMargin + vertMargin / 2
                    }

                    // Scale the page down (between MIN_SCALE and 1).
                    scaleX = scaleFactor
                    scaleY = scaleFactor

                    // Fade the page relative to its size.
                    alpha = (MIN_ALPHA +
                            (((scaleFactor - MIN_SCALE) / (1 - MIN_SCALE)) * (1 - MIN_ALPHA)))
                }
                else -> { // (1,+Infinity]
                    // This page is way off-screen to the right.
                    alpha = 0f
                }
            }
        }
    }
}

자바

public class ZoomOutPageTransformer implements ViewPager2.PageTransformer {
    private static final float MIN_SCALE = 0.85f;
    private static final float MIN_ALPHA = 0.5f;

    public void transformPage(View view, float position) {
        int pageWidth = view.getWidth();
        int pageHeight = view.getHeight();

        if (position < -1) { // [-Infinity,-1)
            // This page is way off-screen to the left.
            view.setAlpha(0f);

        } else if (position <= 1) { // [-1,1]
            // Modify the default slide transition to shrink the page as well.
            float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position));
            float vertMargin = pageHeight * (1 - scaleFactor) / 2;
            float horzMargin = pageWidth * (1 - scaleFactor) / 2;
            if (position < 0) {
                view.setTranslationX(horzMargin - vertMargin / 2);
            } else {
                view.setTranslationX(-horzMargin + vertMargin / 2);
            }

            // Scale the page down (between MIN_SCALE and 1).
            view.setScaleX(scaleFactor);
            view.setScaleY(scaleFactor);

            // Fade the page relative to its size.
            view.setAlpha(MIN_ALPHA +
                    (scaleFactor - MIN_SCALE) /
                    (1 - MIN_SCALE) * (1 - MIN_ALPHA));

        } else { // (1,+Infinity]
            // This page is way off-screen to the right.
            view.setAlpha(0f);
        }
    }
}

심도 페이지 변환기

이 페이지 변환기에서는 페이지를 왼쪽으로 넘길 때에는 기본 슬라이드 애니메이션을 사용하고, 페이지를 오른쪽으로 넘길 때에는 '심도' 애니메이션을 사용합니다. 이 심도 애니메이션은 페이지를 페이드아웃하고 선형으로 축소합니다.

그림 3. DepthPageTransformer
 

심도 애니메이션에서는 기본 애니메이션 (화면 슬라이드)이 여전히 있기 때문에, 음수의 X 변환을 지정하여 화면 슬라이드를 차단해야 합니다. 예를 들면 다음과 같습니다.

Kotlin

view.translationX = -1 * view.width * position

자바

view.setTranslationX(-1 * view.getWidth() * position);

다음은 작동하는 페이지 변환기에서 기본 화면 슬라이드 애니메이션을 차단하는 방법을 보여주는 예제입니다.

Kotlin

private const val MIN_SCALE = 0.75f

@RequiresApi(21)
class DepthPageTransformer : ViewPager2.PageTransformer {

    override fun transformPage(view: View, position: Float) {
        view.apply {
            val pageWidth = width
            when {
                position < -1 -> { // [-Infinity,-1)
                    // This page is way off-screen to the left.
                    alpha = 0f
                }
                position <= 0 -> { // [-1,0]
                    // Use the default slide transition when moving to the left page.
                    alpha = 1f
                    translationX = 0f
                    translationZ = 0f
                    scaleX = 1f
                    scaleY = 1f
                }
                position <= 1 -> { // (0,1]
                    // Fade the page out.
                    alpha = 1 - position

                    // Counteract the default slide transition.
                    translationX = pageWidth * -position
                    // Move it behind the left page.
                    translationZ = -1f

                    // Scale the page down (between MIN_SCALE and 1).
                    val scaleFactor = (MIN_SCALE + (1 - MIN_SCALE) * (1 - Math.abs(position)))
                    scaleX = scaleFactor
                    scaleY = scaleFactor
                }
                else -> { // (1,+Infinity]
                    // This page is way off-screen to the right.
                    alpha = 0f
                }
            }
        }
    }
}

Java

@RequiresApi(21)
public class DepthPageTransformer implements ViewPager2.PageTransformer {
    private static final float MIN_SCALE = 0.75f;

    public void transformPage(View view, float position) {
        int pageWidth = view.getWidth();

        if (position < -1) { // [-Infinity,-1)
            // This page is way off-screen to the left.
            view.setAlpha(0f);

        } else if (position <= 0) { // [-1,0]
            // Use the default slide transition when moving to the left page.
            view.setAlpha(1f);
            view.setTranslationX(0f);
            view.setTranslationZ(0f);
            view.setScaleX(1f);
            view.setScaleY(1f);

        } else if (position <= 1) { // (0,1]
            // Fade the page out.
            view.setAlpha(1 - position);

            // Counteract the default slide transition.
            view.setTranslationX(pageWidth * -position);
            // Move it behind the left page
            view.setTranslationZ(-1f);

            // Scale the page down (between MIN_SCALE and 1).
            float scaleFactor = MIN_SCALE
                    + (1 - MIN_SCALE) * (1 - Math.abs(position));
            view.setScaleX(scaleFactor);
            view.setScaleY(scaleFactor);

        } else { // (1,+Infinity]
            // This page is way off-screen to the right.
            view.setAlpha(0f);
        }
    }
}

추가 리소스

ViewPager2에 관해 자세히 알아보려면 다음 추가 리소스를 참조하세요.

샘플

동영상