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
'Android' 카테고리의 다른 글
RecyclerView) BaseAdapter, BaseViewMdoel, BaseModel, DiffUtil : 공통 어답터와 뷰홀더를 이용해 리사이클러뷰 사용하기 (0) | 2021.08.04 |
---|---|
BottomNavigationView를 이용한 Fragment 전환 (0) | 2021.08.02 |
DI (Koin)을 이용한 의존성 주입 (0) | 2021.08.02 |
Android Studio : 유용한 단축키 (0) | 2021.07.11 |
Parcelable과 Serializable란? (0) | 2021.07.08 |