Swipe View
Swipe View는 손가락의 가로 동작이나 탭, Swipe를 이용하여 동위 화면 간에 탐색(가로 페이징 Horizontal Paging)을 할 수 있는 뷰를 의미한다.
Swipe View를 생성하기 위해서 ViewPager2를 사용한다.
ViewPager2
- Gradle dependency
//viewPagar2 implementation "androidx.viewpager2:viewpager2:1.0.0"
- <ViewPager2> Layout 요소 추가.
SwipeView가 들어갈 UI xml 파일에 ViewPage2 layout을 추가한다.
<FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".swipeview.SwipeViewFragment"> <androidx.viewpager2.widget.ViewPager2 android:id="@+id/viewPager" bindData="@{viewModel.tmpList}" android:layout_width="match_parent" android:layout_height="match_parent" /> </FrameLayout>
ViewPager2.Adatper
다음은 ViewPager2 의 adapter를 설정하기 위한 2가지 방법이다.
- RecyclerView.Adapter
- FragmentStateAdapter
기존 RecyclerView의 Adapter를 설정해줄 때와 같이 RecyclerView.Adapter를 상속한 클래스를 설정해줄 수 있다. 또는 프래그먼트를 Swipe View의 구성요소로 넣고 싶다면 FragmentStateAdapter를 상속한 클래스를 설정해줄 수 있다.
그런데 FragmentStateAdapter도 RecyclerView.Adapter의 하위 클래스기 때문에 ListAdatper 역시 adapter로 사용가능하다.
따라서 ListAdapter를 활용한 ViewPager2, 그리고 FragmentStateAdapter를 활용한 ViewPager2 두 가지 케이스를 정리해보겠다.
ViewPager2.adapter = ListAdapter
우선 ListAdapter를 상속하는 클래스를 정의한다. 이 방법은 블로그에도 포스팅 되어있다.
https://ranseo.tistory.com/246
[Kotlin/Android] RecyclerView (2) DiffUtil & ListAdapter
DiffUtil & ListAdapter DiffUtil 탄생(?)배경 이전에 리사이클러뷰가 보여주는 data 목록에 (추가, 삭제, 변경) 이 발생하면 notifyDataSetChanged() 함수를 사용하여 리사이클러뷰에 알리도록 했다. val data..
ranseo.tistory.com
- class ViewPagerAdapter : ListAdapter
class ViewPagerAdapter : ListAdapter<Tmp, ViewPagerAdapter.ViewHolder>(tmpDiffCallback()){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder.from(parent)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val tmp = getItem(position)
holder.bind(tmp)
}
class ViewHolder(val binding: TmpListItemBinding) : RecyclerView.ViewHolder(binding.root){
fun bind(
tmp: Tmp?
) {
binding.item = tmp
}
companion object{
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = TmpListItemBinding.inflate(layoutInflater, parent, false)
return ViewHolder(binding)
}
}
}
}
class tmpDiffCallback() : DiffUtil.ItemCallback<Tmp>() {
override fun areItemsTheSame(oldItem: Tmp, newItem: Tmp): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Tmp, newItem: Tmp): Boolean {
return oldItem == newItem
}
}
- ViewHolder와 연동할 List_Item_View
<?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>
<variable
name="item"
type="com.example.viewpager2recyclerviewadapter.data.Tmp" />
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text"
android:text="@{item.text}"
android:textSize="32sp"
android:layout_gravity="center"
android:textAlignment="center"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="안녕하세요"/>
<TextView
android:layout_width="24dp"
android:layout_height="52dp"
android:layout_gravity="center|right"
android:background="@color/black"
/>
<TextView
android:layout_width="24dp"
android:layout_height="52dp"
android:layout_gravity="center|left"
android:background="@color/black"
/>
</FrameLayout>
</layout>
※주의 : SwipeView에서 쓰이는 ListItemView의 width, height는 반드시 화면 전체를 채워야 한다. 따라서 match_parent로 정의 되어야 한다. 그렇지 않을 경우 오류.
- In SwipeViewFragment
val adapter = ViewPagerAdapter()
binding.viewPager.adapter = adapter
ViewPager2.adapter = FragmentStateAdapter
SwipeView에 단순 view가 아닌 Fragment 자체를 활용하기 위해 사용되는 Adapter.
FragmentStateAdapter는 현재 adapter가 있는 프래그먼트를 사용해 초기화할 수 있다. 따라서 해당 클래스를 상속하는 하위 클래스는 생성 시 Fragment 매개변수를 부모 클래스에 넘겨주어야 한다.
- SwipeView에 페이징 되는 뷰로 활용될 Fragment 생성 - TmpObjFragment
class TmpObjFragment() : Fragment() { private lateinit var binding: FragmentTmpObjBinding override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { binding = DataBindingUtil.inflate<FragmentTmpObjBinding>(inflater, R.layout.fragment_tmp_obj, container, false) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { arguments?.takeIf { it.containsKey(KEY)}?.apply { binding.item = getParcelable(KEY) } } companion object { val KEY = "object" } }
- arguments?.takeIf{it.containsKey()} 를 이용하여 프래그먼트가 활동주기가 초기화 될 때 (생성되기 직전) 전해 받은 인자가 있다면 이를 사용.
-TmpObjFragment 의 layout
<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> <variable name="item" type="com.example.viewpager2fragmentstateadapter.data.Tmp" /> </data> <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".swipeview.TmpObjFragment"> <TextView android:id="@+id/text" android:text="@{item.text}" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:textAlignment="center" android:textSize="32sp" tools:text="하이요" /> </FrameLayout> </layout>
- FragmentStateAdapter 생성 - TmpViewPagerAdapter
class TmpViewPagerAdapter(fr: Fragment) : FragmentStateAdapter(fr) { var data : List<Tmp> = listOf() set(value) { field = value notifyDataSetChanged() } override fun getItemCount(): Int = data.size override fun createFragment(position: Int): Fragment { val fragment = TmpObjFragment() fragment.arguments = Bundle().apply { putParcelable(KEY, data[position]) } return fragment } companion object { const val KEY = "object" } }
- FragmentStateAdapter(fr) 상속 = fr :Fragment를 매개변수로 넘겨주어 부모 클래스 초기화
- getItemCount() = view의 개수. ->data의 개수로 설정해주면 된다.
- createFragment() = position에 따라 fragment를 생성해서 리턴하는 함수. 해당 함수 내에서 Fragment를 생성하고 argument를 설정하여 생성된 프래그먼트 내에서 해당 arg를 사용할 수 있다. - In SwipeViewFragment
val adapter = TmpViewPagerAdapter(this) binding.viewPager.adapter = adapter