Android

ViewPager2 + TabLayout + RecyclerView

태인킴 2021. 1. 7. 22:19
반응형


ViewPager2 + TabLayout

ViewPager2와 TabLayout를 이용해서 위 영상과 같이 만들어 보려고 합니다. 영상 처럼 좌우로 스와이프도 되고, 특히나 2번째 페이지에서는 RecyclerView를 이용해서 수직으로 아이템을 출력 시켜줍니다.

 

 

1. ViewPager2

ViewPager2의 어떤 Adapter를 붙이는지에 따라서 Fragment, RecyclerView.ViewHolder의 뷰를 적용 할 수 있습니다.

ViewPager

ViewPager2

Pager 아이템

PagerAdapter

RecyclerView.Adapter

RecyclerView.ViewHolder

FragmentStatePagerAdapter

FragmentStateAdapter

Fragment

ViewPager2는 ViewPager와 다르게 RecyclerView를 기반으로 만들어 졌습니다.

 

ViewPager2 상속 구조

public final class ViewPager2 extends ViewGroup {
        private void initialize(Context context, AttributeSet attrs) {
            mRecyclerView = new RecyclerViewImpl(context);
            mLayoutManager = new LinearLayoutManagerImpl(context);
            mRecyclerView.setLayoutManager(mLayoutManager);
            mPagerSnapHelper = new PagerSnapHelperImpl();
            mPagerSnapHelper.attachToRecyclerView(mRecyclerView);
            . . .
    	}
}

ViewPager2의 initialize 메소드를 확인하면, RecyclerView를 초기화 하고, PagerSnapHelper를 사용하여 Pager들의 애니메이션을 구현하고 있는것을 확인 할수 있습니다.

따라서, ViewPager2의 큰 장점들은 아래와 같습니다.

  • RTL (right to left) layout support

  • Vertical orientation support

  • Reliable Fragment support

  • Dataset change animations

이번 포스팅에서는 FragmentStateAdapter를 이용해서 Fragment를 ViewPager의 아이템으로 구현해 보겠습니다.

 

 

1. MainActivity.kt

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // binding SetUp
        val binding = (DataBindingUtil.setContentView(
            this, R.layout.activity_main) as ActivityMainBinding)
            .apply {
                lifecycleOwner = this@MainActivity
            }

        // 1.FragmentStateAdapter 초기화
        val pagerAdapter = PagerFragmentStateAdapter(this)
            .apply {
                addFragment(FirstFragment())
                addFragment(SecondFragment())
                addFragment(ThirdFragment())
            }

        // 2.ViewPager2의 Adapter 설정
        val viewPager: ViewPager2 = binding.pager.apply {
            adapter = pagerAdapter
            registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
                override fun onPageSelected(position: Int) {
                    super.onPageSelected(position)
                    Log.d("ViewPagerFragment", "Page ${position+1}")
                }
            })
        }

        // 3.TabLayout과 ViewPager 연결
        TabLayoutMediator(binding.tabLayout, viewPager) { tab, position ->
            tab.text = "Tab ${position + 1}"
        }.attach()
    }
}

1. FragmentStateAdapter를 초기화 해줍니다. (PagerFragmentStateAdapter는 FragmentStateAdapter를 상속받습니다)

2. ViewPager2의 Adapter를 설정해 줍니다. registerOnPageChangeCallbakc() 또한 설정해 주어, page가 변결될때 이벤트를 받을수 있습니다.

3. TabLayout과 ViewPager 연결 시켜 줍니다. TabLayoutMediator를 사용하여, 연결 시켜 줍니다. 그리고 attach() 함수를 호출시켜 주어야 합니다.

 

 

2. activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<layout
    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"
    >
    <data>
    </data>
    <androidx.constraintlayout.widget.ConstraintLayout 
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <com.google.android.material.tabs.TabLayout
            android:id="@+id/tabLayout"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:background="@color/colorTabLayout"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            />

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

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

ViewPager2와 TabLayout을 원하는 위치에 넣어 줍니다. 여기서 저는 제일 많이 헤맸습니다. ViewPager2는 [width / height]가 [match_parent] 이여야 합니다. 하지만 위와 같이 height를 0dp로 넣어도 저는 문제 없이 잘됩니다. 하지만 저의 다른 프로젝트의 ViewPager2는 위와 같이 height는 0dp가 되면 이상한 버그가 생깁니다. 그 프로젝트의 경우에는 height도 match_parent로 변경하고, 상위 layout을 LinearLayout으로 변경해주니깐 버그가 사라졌습니다.

 

 

3. PagerFragmentStateAdapter.kt

class PagerFragmentStateAdapter(fragmentActivity: FragmentActivity)
    : FragmentStateAdapter(fragmentActivity) {

    private var fragments : ArrayList<Fragment> = ArrayList()

    override fun getItemCount(): Int {
        return fragments.size
    }

    override fun createFragment(position: Int): Fragment {
        return fragments[position]
    }

    fun addFragment(fragment: Fragment) {
        fragments.add(fragment)
        notifyItemInserted(fragments.size - 1)
    }

    fun removeFragment() {
        fragments.removeAt(fragments.size - 1)
        notifyItemRemoved(fragments.size - 1)
    }
}

PagerFragmentStateAdapter는 FragmentStateAdapter를 상속 받아 만든 Pager Adapter 입니다. ArrayList<Fragment>를 멤버 변수로 가지고 있고, 여기에 Fragment들을 Pager가 출력시켜주고, 스와이프 및 애니메이션을 지원해 줍니다.

 

 

4. SecondFragment.kt

class SecondFragment : Fragment(){

    private lateinit var viewModel: SecondItemViewModel

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        return FragmentSecondBinding.inflate(
            inflater,
            container,
            false
        ).apply {
            lifecycleOwner = viewLifecycleOwner
            viewModel = requireActivity().obtainViewModel(SecondItemViewModel::class.java)
            vm = viewModel
            recyclerView.apply {
                setHasFixedSize(true)
                adapter = SecondRecyclerViewAdapter(arrayListOf())
                layoutManager = LinearLayoutManager(requireActivity())
            }
        }.root
    }

    override fun onStart() {
        super.onStart()
        viewModel.onStart()
    }

    fun <T : ViewModel> FragmentActivity.obtainViewModel(viewModelClass: Class<T>) =
        ViewModelProvider(viewModelStore,
            ViewModelFactory.getInstance(
                application
            )
        ).get(viewModelClass)
}

SecondFragment는 영상에서 보셨듯이, 하단의 수직 RecyclerView를 가지고 있습니다. RecyclerView를 초기화 시켜주고, lifecycleOwner도 초기화 시켜 줍니다.

 

 

5. SecondRecyclerViewAdapter.kt

class SecondRecyclerViewAdapter(private var items: List<Item>)
    :RecyclerView.Adapter<SecondRecyclerViewAdapter.VerticalViewHolder>(){

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VerticalViewHolder {
        return VerticalViewItemBinding
            .inflate(LayoutInflater.from(parent.context), parent, false)
            .run {
                VerticalViewHolder(this)
            }
    }

    override fun getItemCount(): Int {
        return items.size
    }

    override fun onBindViewHolder(holder: VerticalViewHolder, position: Int) {
        holder.bind(items[position])
    }

    fun setItem(itemList: List<Item>) {
        items = itemList
        notifyDataSetChanged()
    }

    // ViewHolder
    class VerticalViewHolder(private val binding: VerticalViewItemBinding)
        :RecyclerView.ViewHolder(binding.root){

        fun bind(item: Item) {
            binding.item = item
        }
    }
}

SecondRecyclerViewAdapter에서도 특별한건 없습니다. 다른 RecyclerView.Adapter와 같은 형태로 만들어 줍니다. ViewHolder도 만들어 줍니다.

 

 

6. Item.kt

data class Item (
    val title: String = "sample title",
    val content: String = "sample content"
)

SecondRecyclerView에서 사용할 data class도 만들어 줍니다.

 

 

7. 참고

 

Exploring the View Pager 2

This was originally posted on joebirch.co

medium.com

 

[Android/Kotlin] ViewPager2 - FragmentStateAdapter 사용하기 (with TabLayout)

이전 글에서 RecyclerView.Adapter을 이용하여 ViewPager2를 만드는 방법에 대해 알아봤다. 이번에는 FragmentStateAdapter를 이용하여 ViewPager2를 만들고 TabLayout을 붙여보려고 한다. ViewPager에서 봤듯 Fra..

furang-note.tistory.com

 

 

8. 위 Github 소스코드

 

world9604/ViewPager2Test

ViewPager2 API 연습. Contribute to world9604/ViewPager2Test development by creating an account on GitHub.

github.com

반응형