[android] viewpager2

sundays·2022년 11월 22일
0

android

목록 보기
3/18
post-custom-banner

안녕하세요. 이번에는 레이아웃에 viewpager를 적용하기 위해 해당 라이브러리를 구현해 보았습니다. 해당 viewpager를 compose 에 넣어서 만들기 좋은 라이브러리가 있습니다만 저는 이 라이브러리를 사용하지 않고 jetpack의 viewpager2로만 기능을 구성하려 합니다.

Viewpager2

저는 옛날 안드로이드에서 viewpager를 사용한 기억이 있는데요. viewpager2에서 개선된 점을 설명 드리겠습니다.

세로 방향 지원

  • android:orientation 속성을 설정하여 ViewPager2 요소의 세로 페이징을 사용 설정할 수 있습니다.
  • setOrientation() 메서드를 사용하여 프로그래밍 방식으로 이 속성을 설정할 수도 있습니다

오른쪽에서 왼쪽 지원

  • android:layoutDirection 속성을 설정하여 ViewPager2 요소의 RTL 페이징을 수동으로 사용 설정할 수도 있습니다.

수정 가능한 프래그먼트 컬렉션

  • fragment collection 으로 페이징을 지원하여 기본 컬렉션이 변경되면 notifyDatasetChanged()를 호출하여 UI를 업데이트합니다.

RecyclerView 기반

  • DiffUtil 유틸 클래스에 엑세스 가능하여 데이터 세트 변경 애니메이션을 활용 할 수 있습니다

라이브러리 정리

  • Super Class
    • view paging : RecyclerView.Adapter
    • fragment paging : FragmentStateAdapter
      • createFragment()
  • getCount() => getItemCount()

Use

Fragment(ViewPager)

class CollectionDemoFragment : Fragment() {
    // When requested, this adapter returns a DemoObjectFragment,
    // representing an object in the collection.
    private lateinit var demoCollectionAdapter: DemoCollectionAdapter
    private lateinit var viewPager: ViewPager2

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.collection_demo, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        demoCollectionAdapter = DemoCollectionAdapter(this)
        viewPager = view.findViewById(R.id.pager)
        viewPager.adapter = demoCollectionAdapter
    }
}

Fragment(PageItem)

private const val ARG_OBJECT = "object"

// Instances of this class are fragments representing a single
// object in our collection.
class DemoObjectFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        return inflater.inflate(R.layout.fragment_collection_object, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        arguments?.takeIf { it.containsKey(ARG_OBJECT) }?.apply {
            val textView: TextView = view.findViewById(android.R.id.text1)
            textView.text = getInt(ARG_OBJECT).toString()
        }
    }
}

PagingAdapter

class DemoCollectionAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {

    override fun getItemCount(): Int = 100

    override fun createFragment(position: Int): Fragment {
        // Return a NEW fragment instance in createFragment(int)
        val fragment = DemoObjectFragment()
        fragment.arguments = Bundle().apply {
            // Our object is just an integer :-P
            putInt(ARG_OBJECT, position + 1)
        }
        return fragment
    }
}

TabLayout 추가

viewpage2 위에 tabLayout 을 추가 하면 됩니다.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.google.android.material.tabs.TabLayout
        android:id="@+id/tab_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

</LinearLayout>

그러고 나서 viewpager와 함께 binding 하면 되는데요. 해당 방법은 paging fragment에서 TabLayoutMediator 을 만들어 매개변수에 두고 text값으로 된 데이터 값을 주는 방식입니다. 이 text는 title이 됩니다.

	override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        ...
        TabLayoutMediator(binding.tabLayout, binding.pager) { tab, position ->
            tab.text = "OBJECT ${(position + 1)}"
        }.attach()
        ...
    }

주의

제가 viewpager를 적용하면서 만난 독특한 에러를 소개해드리겠습니다.

Manifest.xml

저는 tabLayout을 넣어주고 따로 해당 activity에 테마를 새로 지정해 주지 않았더니 theme을 지정해 달라는 에러가 발생하였습니다. Theme.appCompat 테마를 지정해주면 된다고 합니다.

Caused by: java.lang.IllegalArgumentException: The style on this component requires your app theme to be Theme.AppCompat (or a descendant).
 at com.google.android.material.internal.ThemeEnforcement.checkTheme(ThemeEnforcement.java:247)
 at com.google.android.material.internal.ThemeEnforcement.checkAppCompatTheme(ThemeEnforcement.java:212)
 at com.google.android.material.internal.ThemeEnforcement.checkCompatibleTheme(ThemeEnforcement.java:147)
 at com.google.android.material.internal.ThemeEnforcement.obtainStyledAttributes(ThemeEnforcement.java:76)
 at com.google.android.material.tabs.TabLayout.<init>(TabLayout.java:525)
 at com.google.android.material.tabs.TabLayout.<init>(TabLayout.java:505)

테마 에러가 나타날때마다 정말 난감한데요. 테마에 대해서도 한번 다루고 싶습니다.
이 에러는 manifest.xml에 딱 한줄 추가하면 되는데요, tab을 사용하게될 actvitiy 에 한줄을 추가합니다.

android:theme="@style/Theme.AppCompat.Light.NoActionBar"

Reference

profile
develop life
post-custom-banner

0개의 댓글