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

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

화면 슬라이드는 전체 화면 간의 전환이며 설정 마법사 또는 슬라이드쇼와 같은 UI에서 일반적으로 사용됩니다. 이 과정에서는 지원 라이브러리에서 제공하는 ViewPager를 사용하여 화면 슬라이드를 실행하는 방법을 보여줍니다. ViewPager 객체는 화면 슬라이드에 자동으로 애니메이션을 적용할 수 있습니다. 다음은 콘텐츠의 한 화면에서 다음 화면으로 전환되는 화면 슬라이드입니다.

화면 슬라이드 애니메이션
 

ViewPager는 AndroidX의 일부입니다. 자세한 내용은 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 android.support.v4.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 android.support.v4.app.Fragment;
...
public class ScreenSlidePageFragment extends Fragment {

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

        return rootView;
    }
}

ViewPager 추가

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

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

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

다음을 수행하는 활동을 만듭니다.

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

Kotlin

import android.support.v4.app.Fragment
import android.support.v4.app.FragmentManager
...
/**
 * 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 mPager: ViewPager

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

        // Instantiate a ViewPager and a PagerAdapter.
        mPager = findViewById(R.id.pager)

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

    override fun onBackPressed() {
        if (mPager.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.
            mPager.currentItem = mPager.currentItem - 1
        }
    }

    /**
     * A simple pager adapter that represents 5 ScreenSlidePageFragment objects, in
     * sequence.
     */
    private inner class ScreenSlidePagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) {
        override fun getCount(): Int = NUM_PAGES

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

자바

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
...
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 ViewPager mPager;

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

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

        // Instantiate a ViewPager and a PagerAdapter.
        mPager = (ViewPager) findViewById(R.id.pager);
        pagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
        mPager.setAdapter(pagerAdapter);
    }

    @Override
    public void onBackPressed() {
        if (mPager.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.
            mPager.setCurrentItem(mPager.getCurrentItem() - 1);
        }
    }

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

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

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

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

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

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

Kotlin

val mPager: ViewPager = findViewById(R.id.pager)
...
mPager.setPageTransformer(true, ZoomOutPageTransformer())

자바

ViewPager mPager = (ViewPager) findViewById(R.id.pager);
...
mPager.setPageTransformer(true, new ZoomOutPageTransformer());

PageTransformer의 예와 동영상은 페이지 축소 변환기심도 페이지 변환기 섹션을 참고하세요.

페이지 축소 변환기

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

ZoomOutPageTransformer
 

Kotlin

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

class ZoomOutPageTransformer : ViewPager.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 ViewPager.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);
        }
    }
}

심도 페이지 변환기

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

DepthPageTransformer
 

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

Kotlin

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

자바

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

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

Kotlin

private const val MIN_SCALE = 0.75f

class DepthPageTransformer : ViewPager.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
                    scaleX = 1f
                    scaleY = 1f
                }
                position <= 1 -> { // (0,1]
                    // Fade the page out.
                    alpha = 1 - position

                    // Counteract the default slide transition
                    translationX = pageWidth * -position

                    // 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

public class DepthPageTransformer implements ViewPager.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.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);

            // 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);
        }
    }
}