[Android/Kotlin] ViewPager2 - FragmentStateAdapter 사용하기 (with TabLayout)
위 블로그를 참고해서 만들었습니다.
PagerFragmentStateAdapter.kt
package com.example.newsapp
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
class PagerFragmentStateAdapter(fragmentActivity: FragmentActivity) : FragmentStateAdapter(fragmentActivity){
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.removeLast()
notifyItemRemoved(fragments.size)
}
}
fragment_article_screen.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
tools:context=".ArticleScreen">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<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"
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"
**android:layoutDirection="ltr"**
app:layout_constraintTop_toBottomOf="@id/tab_layout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
이때 형광펜한 부분: viewpager에서 layoutdirection ltr로 하면 left → right 방향으로 스와이프함. rtl로 하면 right → left 방향
이후 Fragment들 만들어주기: BusinessFragment, HealthFragment, ScienceFragment, SportsFragment, TechFragment,...
ArticleScreen.kt: 1에서 만든 어댑터 뷰페이저에 연결해주기
package com.example.newsapp
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TableLayout
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.tabs.TabLayout
/**
* A simple [Fragment] subclass.
* Use the [ArticleScreen.newInstance] factory method to
* create an instance of this fragment.
*/
class ArticleScreen : Fragment() {
// TODO: Rename and change types of parameters
**private var viewPager: ViewPager2? = null**
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
**val view:View = inflater.inflate(R.layout.fragment_article_screen, container, false)
viewPager = view.findViewById(R.id.pager)**
return view
}
override fun **onActivityCreated**(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val pagerAdapter = PagerFragmentStateAdapter(requireActivity())
// 6개의 fragment add
pagerAdapter.addFragment(BusinessFragment())
pagerAdapter.addFragment(EntertainmentFragment())
pagerAdapter.addFragment(HealthFragment())
pagerAdapter.addFragment(ScienceFragment())
pagerAdapter.addFragment(SportsFragment())
pagerAdapter.addFragment(TechnologyFragment())
// adapter 연결
viewPager?.adapter = pagerAdapter
viewPager?.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int){
super.onPageSelected(position)
Log.e("ViewPagerFragment", "Page ${position+1}")
}
})
}
}
++ 궁금해서 찾아본 어댑터 관련 설명
TabLayout은 HorizontalScrollView를 확장해서 만듬.
viewpager랑 달리 viewpager2는 탭레이아웃을 child로 넣지 않음.
fragment_article_screen.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
tools:context=".ArticleScreen">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
**<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"
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"
android:layoutDirection="ltr"
app:layout_constraintTop_toBottomOf="@id/tab_layout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
여기서 꽤 시행착오를 반복했는데(viewPager랑 tabLayout 변수 초기화가 nullable로 선언되어서 계속 viewPager?로 쓰였어야 했음(tabLayout? 이렇게).
그런데 TabLayoutMediator에서 입력 변수로 들어갈때 nullable 못 받게 해서 결국 viewPager, tabLayout 둘 다 lateinit으로 선언함.
공식 문서에서는 onActivityCreated(view: View, savedInstanceStates: Bundle?){..} 로 되어있어서 그렇게 했다가 onActivityCreated가 override 메소드가 아니라는 에러메시지를 보고 lateinit을 씀.
ArticleScreen.kt
package com.example.newsapp
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TableLayout
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
/**
* A simple [Fragment] subclass.
* Use the [ArticleScreen.newInstance] factory method to
* create an instance of this fragment.
*/
class ArticleScreen : Fragment() {
// TODO: Rename and change types of parameters
private lateinit var viewPager: ViewPager2
private lateinit var tabLayout: TabLayout
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view:View = inflater.inflate(R.layout.fragment_article_screen, container, false)
viewPager = view.findViewById(R.id.pager)
tabLayout = view.findViewById(R.id.tab_layout)
return view
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val pagerAdapter = PagerFragmentStateAdapter(requireActivity())
// 6개의 fragment add
pagerAdapter.addFragment(BusinessFragment())
pagerAdapter.addFragment(EntertainmentFragment())
pagerAdapter.addFragment(HealthFragment())
pagerAdapter.addFragment(ScienceFragment())
pagerAdapter.addFragment(SportsFragment())
pagerAdapter.addFragment(TechnologyFragment())
// adapter
viewPager.adapter = pagerAdapter
viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int){
super.onPageSelected(position)
Log.e("ViewPagerFragment", "Page ${position+1}")
}
})
// tablayout attach
**TabLayoutMediator(tabLayout, viewPager){ tab, position ->
tab.text = "Tab ${position+1}"
}.attach()**
}
}