ViewPager를 써서 좌우로 스와이프되는 화면 만들기

이번 포스팅에서는 ViewPager를 써서 화면을 좌우로 스와이프할 수 있는 앱을 만들어 보겠습니다.

ViewPager는 페이지를 만들어놓고 유저가 좌우로 스와이프 함으로써 각 페이지를 전환할 수 있게 하는 모듈입니다. 예를들어 페이지 1,2,3을 준비하고 이 페이지들을 ViewPager 어댑터에 전달합니다. 어댑터는 페이지를 순서대로 배열하고 페이지 2를 표시합니다. 페이지 1과 3은 화면 바깥의 좌우로 배치한 뒤 스와이프를 하면 현재 페이지를 밀어내고 화면 밖에서 대기하는 페이지가 ViewPager 화면으로 들어오게 됩니다.

# 화면 구성

우선은 화면을 구성하겠습니다. 화면에는 현재 어떤 페이지인지를 보여주는 인디케이터를 설치하고 나머지 영역을 ViewPager로 지정합니다. 인디케이터는 CircleIndicator라는 외부 라이브러리를 이용하겠습니다.

1
2
3
dependencies {
    implementation 'me.relex:circleindicator:2.1.6'
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <me.relex.circleindicator.CircleIndicator
        android:id="@+id/indicator"
        android:layout_height="48dp"
        android:layout_width="match_parent"
        app:ci_drawable="@drawable/black_radius"
        app:ci_height="7dp"
        app:ci_width="7dp"
        app:ci_margin="4dp" />

    <androidx.viewpager.widget.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

CircleIndicator를 사용시 인디케이터로 사용할 마크를 ci_drawable 특성으로 지정해 주어야 하는데 여기서는 drawable 폴더 안에 black_radius.xml 파일을 추가해서 지정해 주었습니다.

1
2
3
4
5
6
7

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <solid
        android:color="@android:color/black"/>
</shape>

# 프래그먼트 작성

다음은 페이지를 만드는데 사용할 프래그먼트를 작성해줍니다. TestFragment라는 이름의 빈 프래그먼트를 하나 만들고 다음과 같이 이미지뷰와 텍스트뷰를 가지는 화면을 만들어줍니다.

 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
<?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=".TestFragment">


    <ImageView
        android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toTopOf="@+id/textView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:src="@tools:sample/avatars" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView"
        android:textAppearance="@style/TextAppearance.AppCompat.Large"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/imageView" />
  
</androidx.constraintlayout.widget.ConstraintLayout>

TestFragment 클래스의 내용은 기본적으로 만들어지는 코드를 조금만 바꾸어주겠습니다. 외부에서 전달되는 image와 text 값을 newInstance에서 번들형태로 초기화한후 onCreate에서 값을 부여해줍니다. onCreateView에서 화면을 만들고 onViewCreated에서 이미지뷰와 텍스트뷰를 표시하도록 하였습니다.

 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
class TestFragment : Fragment() {
    private var image: Int? = null
    private var text: String? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.let {
            image = it.getInt("image", 0)
            text = it.getString("text", "")
        }
    }

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

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        imageView.setImageResource(image!!)
        textView.text = text
    }

    companion object {
        fun newInstance(image: Int, text: String) =
            TestFragment().apply {
                arguments = Bundle().apply {
                    putInt("image", image)
                    putString("text", text)
                }
            }
    }
}

# ViewPager 어댑터 작성 및 연결

다음으로는 ViewPager Adapter를 만들어줍니다. 별개 클래스이지만 내용이 복잡하지 않으므로 메인액티비티 클래스 안에 만들어줍니다. 어댑터는 FragmentStatePagerAdapter 혹은 FragmentStateAdapter를 상속받아야 하는데 페이지가 많을 경우에는 자원을 동적으로 관리해 주는 FragmentStatePagerAdapter가 적절하고 페이지수가 적다면 FragmentStateAdapter를 사용하면 됩니다. 전체 페이지수는 4개이고 getItem에서 페이지를 선택했을 때 반환할 프래그먼트를 지정해줍니다. 이때 프래그먼트에 전달할 내용을 어댑터 안에서 지정해줍니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class MainActivity : AppCompatActivity() {

    class CustomPagerAdapter(fm: FragmentManager): FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
        private val PAGENUMBER = 4

        override fun getCount(): Int {
            return PAGENUMBER
        }

        override fun getItem(position: Int): Fragment {
            return when (position) {
                0 -> TestFragment.newInstance(R.raw.img00, "test 00")
                1 -> TestFragment.newInstance(R.raw.img01, "test 01")
                2 -> TestFragment.newInstance(R.raw.img02, "test 02")
                else -> TestFragment.newInstance(R.raw.img03, "test 03")
            }
        }
    }
}

마지막으로 메인액티비티에서 ViewPager와 어댑터를 연결하고, 인디케이터와 ViewPager를 연결하면 작성 완료입니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class MainActivity : AppCompatActivity() {
    private var vpAdapter: FragmentStatePagerAdapter? = null

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

        vpAdapter = CustomPagerAdapter(supportFragmentManager)
        viewpager.adapter = vpAdapter

        indicator.setViewPager(viewpager)
    }
}

이렇게해서 ViewPager를 사용하는 법에 대해 알아보았습니다.

Built with Hugo
Theme Stack designed by Jimmy