안드로이드/개발관련(Kotlin)

안드로이드 ViewPager2 + TabLayout를 사용한 스와이프 메뉴

닉네임못짓는사람 2023. 3. 28. 19:33
반응형

이번 글에서는 ViewPager2와 TabLayout을 사용하여 스와이프 가능한 탭 메뉴를 만들어보도록 하자.

 

XML파일 작성


먼저 viewpager와 tabLayout이 있는 메인 액티비티의 XML파일을 작성해주도록 하자.

 

<?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">

    <com.google.android.material.tabs.TabLayout
        android:id="@+id/tab_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"/>

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintTop_toBottomOf="@id/tab_layout"
        app:layout_constraintBottom_toBottomOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

간단하게 viewpager와 tabLayout만 있는 XML을 작성해보았다.

 

Adapter


다음으로 viewpager에 연결할 adapter클래스를 새로 작성한다.

class ViewPagerAdapter(
    private val fragmentList: ArrayList<Fragment>,
    container: AppCompatActivity
): FragmentStateAdapter(container.supportFragmentManager, container.lifecycle) {
    override fun getItemCount(): Int = fragmentList.count()
    override fun createFragment(position: Int): Fragment = fragmentList[position]
}

화면에 표시할 fragment의 리스트와 container를 넣어주었고,

getItemCount는 하면에 표시할 페이지의 개수, createFragment는 각 페이지마다 보여줄 VIew를 반환해주도록 하면 된다.

위 코드에선 fragmentList를 parameter로 넣어주었기 때문에 각 위치의 fragment를 반환해주기만 하면 간단하다.

 

Fragments


위에서 만든 Adapter에 넣을 각 Fragment를 만들어주도록 하자.

class Fragment1: Fragment() {
    private lateinit var binding: Fragment1Binding
    
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        binding = Fragment1Binding.inflate(inflater, container, false)
        return binding.root
    }
}
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/teal_200">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="1번"
        android:textColor="@color/black"
        android:textSize="50dp" />

</FrameLayout>

이 글에선 거창하게 만들 필요 없으니 위와 같이 간단한 fragment를 세 개 만들어주도록 하자.

 

ViewPager + TabLayout


이제 사전 준비가 다 끝났으니 마지막으로 viewpager와 tabLayout을 연결시키는 작업을 해주도록 하자.

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

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

        initView()
    }

    private fun initView() {
        val viewPager = binding.viewpager
        val tabLayout = binding.tabLayout

        val fragmentList = ArrayList<Fragment>()
        fragmentList.add(Fragment1())
        fragmentList.add(Fragment2())
        fragmentList.add(Fragment3())

        viewPager.adapter = ViewPagerAdapter(fragmentList, this)

        val iconList = ArrayList<Drawable?>()
        iconList.add(ContextCompat.getDrawable(this, R.drawable.ic_launcher_foreground))
        iconList.add(ContextCompat.getDrawable(this, R.drawable.ic_launcher_foreground))
        iconList.add(ContextCompat.getDrawable(this, R.drawable.ic_launcher_foreground))

        TabLayoutMediator(tabLayout, viewPager) { tab, position ->
            tab.text = "${position}번"
            tab.icon = iconList[position]
        }.attach()
    }
}

 

코드에서처럼 TabLayoutMediator에 tabLayout과 viewPager를 argument로 주어서 둘을 연결시킬 수 있다.

이때 각 탭의 텍스트와 아이콘 등을 설정해줄 수 있다.

 

순환 페이지 (Circular ViewPager)


위와 같이 만들었을 땐 1번에서 왼쪽으로 스와이프해서 3번으로 가거나

3번에서 오른쪽으로 스와이프해서 1번으로 가는 동작이 불가능하다.

 

이런 동작이 가능하도록 하려면 아래 코드를 위 코드 아래에 추가해주면 된다.

        viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
            var currentPosition = 0
            var currentState = 0
            override fun onPageScrolled(
                position: Int,
                positionOffset: Float,
                positionOffsetPixels: Int
            ) {
                if (currentState == ViewPager2.SCROLL_STATE_DRAGGING && currentPosition == position) {
                    when (currentPosition) {
                        0 -> viewPager.currentItem = fragmentList.lastIndex
                        fragmentList.lastIndex -> viewPager.currentItem = 0
                    }
                }
                super.onPageScrolled(position, positionOffset, positionOffsetPixels)
            }
            override fun onPageSelected(position: Int) {
                currentPosition = position
                super.onPageSelected(position)
            }

            override fun onPageScrollStateChanged(state: Int) {
                currentState = state
                super.onPageScrollStateChanged(state)
            }
        })

 

 

Select Tab


마지막으로 화면 시작할 때 시작 탭을 지정해주고 싶은 경우가 있다.

이런 때 코드에서 해당 tab을 선택해주면 되는데, 아래와 같이 선택할 수 있다.

tabLayout.getTabAt(1)?.select()

이때 문제는 이 방법을 사용하면 tabLayout의 select indicator가 사라진다는 점이다.

 

때문에 탭을 선택할 때에는 아래와 같이 viewPager를 통해서 지정해주면 된다.

viewPager.setCurrentItem(1, false)

이때 두 번째 argument인 smoothScroll을 true로 줄경우 위의 tabLayout때와 같이 indicator가 사라지기 때문에 주의하자.

반응형