본문 바로가기

Android

TabLayout + ViewPager2를 이용한 화면 구성

728x90

TabLayout과 ViewPager2를 이용해 화면을 구성하는 방법을 알아봤습니다. 

 

1. enum Class 생성

string 값을 가진 상수를 선언했습니다. TodoCategory의 상수들로 무언가를 비교하겠지만, 화면에 출력시킬 땐 categoryNameId를 통해 string 값을 가져올 겁니다.

 

enum class TodoCategory(
    @StringRes val categoryNameId: Int
) {
    ALL(R.string.all),
    ANDROID(R.string.android),
    LANGUAGE(R.string.language),
    DB(R.string.db),
    OTHER(R.string.other);
}

 

 

2. Fragment 생성

todoCategory 프로퍼티를 사용하지 않고 Fragment를 생성하도록 작성해도 문제는 없지만, 생성된 Fragment를 구별하고, 각각 원하는 작업을 할 수 있도록 하기위해 Fragment의 인스턴스를 생성할 때 TodoCategory의 value를 받도록 했습니다. 

 

class TodoCategoryFragment : BaseFragment<TodoCategoryViewModel, FragmentTodoCategoryBinding>() {
    private val THIS_NAME = this::class.simpleName

    override val viewModel: TodoCategoryViewModel by viewModel { parametersOf(todoCategory) }

    // HomeFragment에서 newInstance()를 통해 전달한 category값을 Bundle로부터 가져옴
    private val todoCategory by lazy { arguments?.getSerializable(TODO_CATEGORY_KEY) as TodoCategory }

    override fun getDataBinding(): FragmentTodoCategoryBinding
        = FragmentTodoCategoryBinding.inflate(layoutInflater)

    override fun initViews() = with(binding) {
        if (todoCategory == TodoCategory.ANDROID) {
            Log.d(Constants.TAG, "$THIS_NAME initViews() : ${hashCode()}")
            binding.TodoCategoryRecyclerView.setBackgroundColor(Color.BLUE)
        }
    }

    override fun onResume() {
        Log.d(Constants.TAG, "$THIS_NAME onResume() : ${hashCode()}")
        super.onResume()
    }

    override fun observeData()  {

    }

    companion object {
        const val TODO_CATEGORY_KEY = "todoCategory"

        // TodoCategoryFragment 인스턴스를 생성하면서 arguments에 데이터를 넘겨주는 코드
        fun newInstance(todoCategory: TodoCategory) = TodoCategoryFragment().apply {
            arguments = bundleOf(
                TODO_CATEGORY_KEY to todoCategory
            )

            Log.d(Constants.TAG, "$THIS_NAME newInstance() : $arguments, $TODO_CATEGORY_KEY")
        }
    }
}

 

 

3. FragmentStateAdapter()를 상속받아 구현한 FragmentViewPagerAdapter 생성

FragmentStateAdapter는 Fragment의 생명주기를 담당하며, ViewPort에서 Fragment가 너무 멀어지면 소멸되고 상태를 저장했다가, Fragment가 ViewPort에 가까워지면 새 Fragment가 요청되고, 이전의 저장된 상태를 사용해 초기화 합니다.

 

구현할 땐 Fragment에 ViewPager2가 존재하는 경우엔 생성자로 Fragment를 넘겨주어야 합니다. 

 

createFragment() : 지정된 위치와 연결된 새 Fragment를 제공합니다. Fragment

 

class FragmentViewPagerAdapter(
    fragment : Fragment,
    val fragmentList : List<TodoCategoryFragment>
) : FragmentStateAdapter(fragment) {

    override fun getItemCount(): Int = fragmentList.size

    override fun createFragment(position: Int): Fragment
    {
        Log.d(Constants.TAG, " createFragment() : $position, ${fragmentList[position]}")
       return fragmentList[position]
    }
}

 

 

4. TabLayout, ViewPager를 보여줄 HomeFragment

1. todoCategories : enum class로 정의한 Category들의 값을 배열에 저장합니다.

 

2. if문 : viewPagerAdapter가 초기화되지 않았다면, Category 배열만큼 TodoCategoryFragment의 인스턴스 생성한 후, List에 Fragment를 저장합니다.

 

FragmentViewPagerAdapter에 Fragment를 담은 List를 전달하여, 하위 뷰를 담을 컨테이너를 생성해 viewPager에 삽입합니다.

 

3. offscreenPageLimit : ViewPager2가 현재 페이지로부터 얼만큼 떨어진 페이지를 미리 생성할 것인지 설정합니다.

 

4. TabLayoutMediator : TabLayout을 ViewPager2와 연결하는 중재자 역할을 하며, Tab을 선택하면 ViewPager2의 위치와 동기화하고, ViewPager2를 끌면 TabLayout의 스크롤 위치를 동기화 합니다.

 

아래 initViewPager()를 onViewCreate() 생명주기 함수에서 호출하도록 합니다.

 

private lateinit var viewPagerAdapter: FragmentViewPagerAdapter

 private fun initViewPager() = with(binding) {
        val todoCategories = TodoCategory.values()

        if (::viewPagerAdapter.isInitialized.not()) {
            val fragmentList = todoCategories.map {
                Log.d(Constants.TAG, "$THIS_NAME Create TodoCategoryFragment : $it")

                TodoCategoryFragment.newInstance(it)
            }

            viewPagerAdapter = FragmentViewPagerAdapter(
                this@HomeFragment, fragmentList
            )

            viewPager.adapter = viewPagerAdapter
        }

        viewPager.offscreenPageLimit = 1

        TabLayoutMediator(tabLayout, viewPager) { tab, position ->
            Log.d(Constants.TAG, "$THIS_NAME TodoCategory : $position, ${todoCategories[position].name}")
            tab.text = getString(todoCategories[position].categoryNameId)
        }.attach()
    }

 

 

참고 

1. https://developer.android.com/guide/navigation/navigation-swipe-view-2?hl=ko