[안드로이드 공식문서 파헤치기] ViewPager2의 모든 것!

dada·2022년 8월 14일
2
post-thumbnail

✅사전지식

  • [안드로이드 공식문서 파헤치기] ViewPager의 모든 것!에서 살펴본 것처럼 PagerAdapter를 상속한 FragmentPagerAdapter, FragmentStatePagerAdapter를 이용해 ViewPager를 구현할 때, 각 페이지의 item은 fragment인스턴스로 구성해야합니다.

  • 이때 각 페이지의 item을 view로 구성하고 싶은 경우에는 PagerAdapter를 직접 상속받아 구현해야 합니다.

  • ViewPager2는 페이지를 fragment로 구성할 땐 FragmentStateAdapter를, view로 구성할때는 RecyclerView.Adapter를 사용해야 합니다

✅ViewPager2의 변경점

✔ 제공하는 기능 추가

  • Vertical Scrolling 지원: ViewPager는 좌우 슬와이핑만 가능했지만 ViewPager2는 위아래 스와이핑도 지원합니다

  • Right to Left Scrolling: 오른쪽에서 왼쪽으로 읽는 문화권의 나라를 위해 오른쪽->왼쪽 방향의 스와이핑을 지원합니다

✔ 개선된 기능

  • 데이터 갱신 이슈: ViewPager의 문제였던 notifyDataSetChanged의 문제를 해결했습니다

  • View 재활용 가능: ViewPager의 PagerAdapter를 이용해 View를 item으로 할때는 View 재활용이 불가능했지만 ViewPager2에서는 RecyclerView.Adapter를 이용하기 때문에 DiffUtil 통한 View 재활용이 가능합니다

✔ 마이그레이션

  • ViewPager는 더 이상 유지보수 되지 않습니다. ViewPager2로 마이그레이션 해야합니다
  • FragmentPagerAdapter, FragmentStatePagerAdapter
    ->FragmentStateAdapter
  • PagerAdapter->RecyclerView.Adapter
  • getCount()->getItemCount()
  • getItem()->getCreateItem()

✅RecyclerView.Adapter

  • ViewPager2가 View를 그리고, 재사용하는데 RecyclerView.Adapter를 사용한다면, ViewHolder를 재활용하는 매커니즘도 동일할 것이란 예상이 들어 로그를 확인했습니다
    일반적으로 RecyclerView에 적용했을때처럼 onCreateViewHolder()가 일정부분 호출되고 ViewHolder를 생성한 후 onViewRecycled()가 호출되며 ViewHolder가 재활용됩니다

  • 여기서 눈여겨볼만한점은 RecyclerView는 item이 화면밖으로 사라지자마자 해당 position의 View가 화면에서 떨어졌다는 onViewDetachedFromWindow()가 호출되지만, ViewPager2에 사용된 RecyclerView의 item이 화면밖으로 나가면, 해당 position의 2번째 전 position인 View가 onViewDetachedFromWindow()로 화면에서 떨어집니다

  • 이는 ViewPager에서 destroyItem()이 호출될때 해당 position의 2번째 이전 fragment가 remove()되었던 것과 같은 맥락입니다. ViewPager의 특성상 양 사이드의 화면을 살짝 보여줘야할 일이 있을 수 있어서, 바로 item을 제거하지 않는 것입니다.

✅FragmentStateAdapter

  • FragmentStateAdapter는 추상클래스라 하위클래스에서 상속해서 사용해야 하며, RecyclerView.Adapter를 상속하고 있고 StatefulAdapter라는 인터페이스를 구현하고 있습니다.

  • 여기서, ViewPager의 FragmentStatePagerAdapter에서 사용했던 mFragments, mSavedStatesFragmentStateAdapter에서도 사용되는걸 보면 FragmentStateAdapter도 fragment를 새롭게 생성할 때 저장해두었던 state를 이용해 상태를 복원한다는 걸 알 수 있었습니다!!(매커니즘이 비슷한데 RecyclerView.Adapter를 상속한다는게 다르니까 새로 생성한 fragment를 재사용하는거겠군)

  • FragmentStateAdapter를 사용할때는 FragmentStateAdapter를 상속할 땐 추상 메서드인 createFragment()와 RecyclerView.Adapter의getItemCount()를 반드시 오버라이딩해야합니다

👉FragmentStateAdapter.createFragment()

  • FragmentStateAdapter를 상속할 땐 추상 메서드인 createFragment()를 반드시 오버라이딩 해야합니다

  • createFragment()는 파라미터로 전달받은 특정 position 위치에 연결된 프래그먼트 인스턴스를 생성해서 제공합니다. 그러면 FragmentStateAdapter가 반환된 프래그먼트의 라이프 사이클을 책임집니다

  • 반환된 프래그먼트는 아이템을 보여주는데 사용되며, 포커싱된 페이지로부터 너무 멀리 떨어진 position에 연결된 프래그먼트는 상태값만 저장하고 인스턴스는 파괴됩니다. 포커싱된 페이지로부터 점점 가까워지는 position에 대해서는 새로운 프래그먼트를 생성하고 프래그먼트를 저장해놓은 상태값으로 초기화될 것입니다

  • ViewPager2의 setOffscreenPageLimit()에서 프래그먼트 인스턴스 파괴의 기준이 되는 페이지 개수를 정할 수 있습니다

  • 즉 position별로 서로 다른 프래그먼트를 보여줘야 하면 서로 다른 프래그먼트를 생성해서 반환하고 모든 position에 똑같은 프래그먼트를 계속 보여줘야 하면 계속 같은 프래그먼트를 생성해서 반환하면 됩니다

👉RecyclerView.Adapter.getItemCount()

  • FramgmentStateAdapter가 ReyclerView.Adapter를 상속하고 있기때문에 RecyclerView.Adapter에 있는 추상 메드를 하위 클래스에서 오버라이딩 해줘야합니다. 그런데getItemCount()FramgmentStateAdapter가 오버라이딩 하고 있지 않아서 FragmentStateAdapter를 상속한 하위 클래스가 오버라이딩 해줘야 합니다

  • 주석을 보면, getItemCount()는 adapter의 item개수를 리턴하라고 되어있습니다

  • RecyclerView에서의 adapter item은 data-set의 사이즈이지만, ViewPager2에서의 adapter item은 페이지의 개수이므로 생성될 framgment 인스턴스의 개수를 반환하면 됩니다

✅그외 RecyclerView의 콜백 메서드는?

  • RecyclerView.Adapter를 사용해보신 분들이라면 RecyclerView.AdaptergetItemCount말고도 추상메서드인 onCreateViewHolder(), onBindViewHolder()를 오버라이딩해서 콜백을 정의하고 ViewHolder class를 직접 만들어주었던 경험이 있을 것입니다.

  • ViewPager2는 FragmentStateAdapter가 이러한 RecyclerView의 라이프사이클을 정의하는 콜백함수들을 직접 오버라이딩하고 있기 때문에 우리가 따로 정의할 필요가 없었던 것입니다.(getItemCount()제외)

  • FramgnetStateAdapter는 우리가 오버라이딩해야했던 onCreateViewHolder(), onBindViewHolder()뿐만 아니라 RecyclerView.Adapter에 정의된 여러 콜백함수인 onViewRecycled(리사이클러뷰가 재활용할 ViewHolder를 가져온 시점에 콜백), onAttachedToWindow(아이템 뷰를 리사이클러뷰에 attach한 시점에 호출되는 콜백)등을 직접 오버라이딩하고 있습니다. 각 콜백메서드를 살펴봅시다!

👉FramgnetStateAdapter.onCreateViewHolder()

  • ViewHolder는 FragmentViewHolder라는 클래스로 고정되어있고(viewType이 1개 뿐이군,,하긴 여러개일 필요가 없으니) 재미있는건 FrameLayout을 container로 사용한다는 것입니다

  • 즉 빈 FrameLayout을 ViewHolder로써 생성하는 작업을 하고 있도록 onCreateViewHolder()가 오버라이딩 되어있습니다

👉FramgnetStateAdapter.onBindViewHolder()

  • onBindViewHolder()의 파라미터로 전달된 position 위치에 사용될 프래그먼트 생성 요청을 합니다.

  • 이때 요청할 프래그먼트를 이미 메모리에 가지고 있으면(이것도 RecyclerView처럼 RecycledPool, cache 에 저장해두는건가? 그것을 재사용하고( 아무튼 FragmentStatePagerAdapter랑 확실히 이부분에서 다르군) 프래그먼트 인스턴스는 없지만 상태값을 가지고 있다면 프래그먼트 생성 요청을 한 후 상태값을 재사용합니다

👉FramgnetStateAdapter.onAttachedToWindow()

  • onBindViewHolder()에서 요청한 프래그먼트를 ViewHolder의 FrameLayout에 붙이는 작업을 합니다

👉FramgnetStateAdapter.onViewRecycled()

  • 재사용할 뷰홀더에 붙어있는 프래그먼트의 상태값은 저장하고 인스턴스는 제거하는 작업을 합니다(RecyclerView.Adapter가 오버라이딩했을땐 onViewRecycled()는 재활용할 ViewHolder를 가져온 시점에 콜백되는 함수였는데)

즉, FramgnetStateAdapter는
1. FragmentStatePagerAdapter와 다른점:
RecyclerView.Adapter를 상속하므로->ViewHodler를 재활용(FrameLayout)
2. FragmentPagerAdapter와 다른점:
fragment 인스턴스를 페이지 수만큼 생성해 메모리에 올려두는게 아니라 position에 가까워지면 생성하고 멀어지면 remove

✅FragmentStateAdapter 생성자

  • FragmentStateAdapter는 총 3개의 생성자 함수가 있고 상황에 따라 다른 생성자를 사용해야 합니다

👉1. fragmentManager, lifecycle

    public FragmentStateAdapter(@NonNull FragmentManager fragmentManager,
            @NonNull Lifecycle lifecycle) {
        mFragmentManager = fragmentManager;
        mLifecycle = lifecycle;
        super.setHasStableIds(true);
    }
  • 해당 생성자 함수는 다른 두 생성자 함수 내부에서 계속 호출되는 생성자 함수이기때문에 가장 중요합니다

  • FragmentStateAdapter는 각 페이지의 item이 되는 framgment를 관리해야하므로 FragmentManager가 필요하니 이걸 파라미터로 넘겨줘야 합니다

  • lifecycleFragmentStateAdapter 클래스 내우베어 Lifecycle observe를 달아서 특정 수명 주기 시점인 경우 적절한 작업을 하기 위해 전달받습니다

👉2. fragmentActivity

    public FragmentStateAdapter(@NonNull FragmentActivity fragmentActivity) {
        this(fragmentActivity.getSupportFragmentManager(), fragmentActivity.getLifecycle());
    }
  • FragmentActivity는 Fragment가 호스팅되어있는 Activity인 인스턴스를 받습니다

    • Activity가 AppCompatActivity 클래스를 상속하고 있고 AppCompatActivity가 FragmentActivity를 상속하고 있습니다
  • 내부적으로 1번 생성자 함수를 호출하고 있는데 이때 인자로 받은 Activity로부터 FragmentManager와 Activity의 lifecycler을 넘겨서 1번 생성자 함수의 파라미터로 전달합니다

  • ViewPager2를 Activity에 배치할때 사용해야합니다. Activity에서 FragmentManager를 얻어 사용하고, Activity의 lifecycler을 전달할 수 있도록 하기 위함입니다

👉3. fragment

    public FragmentStateAdapter(@NonNull Fragment fragment) {
        this(fragment.getChildFragmentManager(), fragment.getLifecycle());
    }
  • 프래그먼트의 인스턴스를 전달받고 내부적으로 1번 생성자 함수를 호출합니다
  • 이때 프래그먼트의 childFragmentManager, 프래그먼트의 lifecycler을 넘깁니다
  • ViewPager2를 Fragment에 배치할때 사용해야합니다.
profile
'왜?'라는 물음을 해결하며 마지막 개념까지 공부합니다✍

0개의 댓글