ViewPager2와 TabLayout을 이용해 스와이프 되는 화면 구현하기

이번 포스팅에서는 ViewPager2TabLayout을 이용해 스와이프 되는 화면을 구현해 보도록 하겠습니다.

ViewPager2를 사용하는 이유나 장점에 대해서는 ViewPager1 프로젝트를 ViewPager2 프로젝트로 변환하기 포스팅에서 다루었으니 그 내용을 참조해 주시기 바랍니다.

# 플러그인 추가

우선은 뷰바인딩 사용설정 및 ViewPager2 플러그인을 추가합니다.

1
2
3
4
5
6
7
8
9
// 뷰바인딩 사용준비
android {
    buildFeatures.viewBinding true
}

// ViewPager2 라이브러리 추가
dependencies {
    implementation 'androidx.viewpager2:viewpager2:1.0.0'
}

# Fragment의 레이아웃 작성

다음은 new -> fragment 를 선택하여 테스트용으로 사용할 프래그먼트를 만들어줍니다. 텍스트를 화면 중간에 표시하기 위해 코드를 약간 변경합니다.

1
2
3
4
5
6
7
<TextView
    android:id="@+id/textview"
    android:layout_gravity="center"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/hello_blank_fragment"
    android:textAppearance="@style/TextAppearance.AppCompat.Large" />

# Fragment 클래스 작성

기본 프래그먼트에 뷰 바인딩을 적용하고 텍스트를 표시하도록 변경합니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class BlankFragment : Fragment() {
    private var _binding: FragmentBlankBinding? = null
    private val binding get() = _binding!!
    
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?,
    ): View? {
        // Inflate the layout for this fragment
        _binding = FragmentBlankBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        binding.textview.text = param1
    }
}

# ViewPager2의 PagerAdapter 작성

방금 작성한 프래그먼트에 텍스트를 전해주는 ViewPager2 어댑터를 작성합니다. 여기서는 3개의 페이지를 갖도록 하겠습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class MyPagerAdapter(fa: FragmentActivity): FragmentStateAdapter(fa) {
    private val NUM_PAGES = 3

    override fun getItemCount(): Int = NUM_PAGES

    override fun createFragment(position: Int): Fragment {
        return when (position) {
            0 -> { BlankFragment.newInstance("Page 1","")}
            1 -> { BlankFragment.newInstance("Page 2","")}
            else -> { BlankFragment.newInstance("Page 3","")}
        }
    }
}

# activity_main.xml

메인 액티비티의 화면을 구성합니다. 화면 위쪽에 ViewPager를 배치하고 나머지 공간에 AppBarLayout을 배치한 뒤 그 내부에 TabLayout을 배치합니다. 그리고 TabLayout의 app::tab~ 속성을 사용해서 색을 지정합니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?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"
    android:orientation="vertical">

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/viewpager"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:orientation="horizontal"
        app:layout_constraintBottom_toTopOf="@+id/appBarLayout"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <com.google.android.material.appbar.AppBarLayout
        android:id="@+id/appBarLayout"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:theme="@style/Theme.AppCompat.Light.DarkActionBar"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent">

        <com.google.android.material.tabs.TabLayout
            android:id="@+id/tabs"
            android:layout_width="match_parent"
            android:layout_height="60dp"
            android:background="@color/white"
            app:tabIndicatorColor="@color/design_default_color_primary"
            app:tabRippleColor="@color/design_default_color_primary"
            app:tabSelectedTextColor="@color/design_default_color_primary"
            app:tabTextColor="@color/black" />
    </com.google.android.material.appbar.AppBarLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

# Mainactivity 설정

ViewPager 어댑터 클래스의 인스턴스를 만들어 ViewPager 어댑터 속성에 연결시킵니다. 그리고 TabLayoutMediator를 이용해서 TabLayout과 ViewPager를 연결합니다. 각 탭의 타이틀과 아이콘은 블럭내부에서 지정할 수 있습니다. 탭의 이름은 Title 0 형식으로 지정하고 아이콘은 벡터 이미지를 추가한 뒤 setIcon 에서 불러오도록 하였습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.viewpager.apply {
            adapter = MyPagerAdapter(context as FragmentActivity)
        }

        TabLayoutMediator(binding.tabs, binding.viewpager) { tab, position ->
            tab.text = "Title $position"
            when (position) {
                0 -> tab.setIcon(R.drawable.ic_baseline_format_list_bulleted_24)
                1 -> tab.setIcon(R.drawable.ic_baseline_map_24)
                2 -> tab.setIcon(R.drawable.ic_baseline_info_24)
            }
        }.attach()
    }
}

# PageTransformer 설정

ViewPager에서는 페이지를 변경할때 여러가지 효과를 줄 수 있습니다. 구글에서 공개하는 ZoomOutPageTransformer를 사용한다면 스크롤할때 페이지가 축소되면서 페이드아웃되고, 페이지가 중앙에 가까워지면 원래 크기로 다시 커지면서 페이드인 되는 효과를 낼 수 있습니다.

그 외에도 여러가지 페이지 전환효과를 사용하고 싶다면 33 Viewpager2 Transformers for Your Android ui’s 페이지를 참고하세요.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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 = max(MIN_SCALE, 1 - 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
                }
            }
        }
    }
}

// MainActivity
binding.viewpager.apply {
    setPageTransformer(ZoomOutPageTransformer())
}

이렇게 해서 ViewPager2와 TabLayout을 조합한 디자인을 구축하는 법에 대해 알아보았습니다.

Built with Hugo
Theme Stack designed by Jimmy