Slide 1

Slide 1 text

ViewPager2について調べてみた Kazuki Nishida(@kazkn_24)

Slide 2

Slide 2 text

みんな大好きViewPager ● ViewPagerとはAndroidにおいて横方向のページャー画面を実装する為に使われる Widget ● ListViewやRecyclerViewと同じようにAdapterを用いて表示要素の取得を行う ● ViewPager用のAdapterとしてはFragmentPagerAdapterもしくは FragmentStatePagerAdapterを用いて1ページ=1Fragmentとして実装を行う for Beginners

Slide 3

Slide 3 text

ViewPager2とは ● RecyclerViewの機能を用いて作成された新しいViewPager ● AdapterはRecyclerViewと同じRecyclerView.Adapterを継承したものを使用する ● RTLレイアウト対応 ● 縦方向のPager機能も簡単時実現可能 ● 現時点(2019/03/10)では1.0.0-alpha01 https://developer.android.com/jetpack/androidx/releases/viewpager2

Slide 4

Slide 4 text

RecyclerViewとの関係

Slide 5

Slide 5 text

レシピ名 public class ViewPager2 extends ViewGroup { private RecyclerView mRecyclerView; … private void initialize(Context context, AttributeSet attrs) { mRecyclerView = new RecyclerView(context) { … }; mRecyclerView.setId(ViewCompat.generateViewId()); mLayoutManager = new LinearLayoutManager(context); mRecyclerView.setLayoutManager(mLayoutManager); setOrientation(context, attrs); … attachViewToParent(mRecyclerView, 0, mRecyclerView.getLayoutParams()); } public final void setAdapter(@Nullable Adapter adapter) { mRecyclerView.setAdapter(adapter); } } 内部にRecyclerViewを持っている ● ViewGroupを継承しており、子Viewとして RecyclerView生成する ● LayoutManagerはLinerLayoutManager固定 ● ViewPager2にセットしたAdapterは単純に内 部のRecyclerViewにセットされるだけ ● RecyclerViewは(いまのところ)外部からは取 得する方法なし

Slide 6

Slide 6 text

レシピ名 public class ViewPager2 extends ViewGroup { private RecyclerView mRecyclerView; … private void initialize(Context context, AttributeSet attrs) { … new PagerSnapHelper().attachToRecyclerView(mRecyclerView); … } } ページスナップ(1ページごとにスクロールが 止まるやつ) ● PagerSnapHelperクラスで実現されているお り、PagerSnapHelper内部でRecyclerViewに 対してScrollListenerなどのコールバックを設 定している ● attachToRecyclerView()のパラメータは RecyclerViewなのでViewPager2とは関係な いRecyclerViewにも適用は可能?

Slide 7

Slide 7 text

使い方

Slide 8

Slide 8 text

レシピ名 viewPager.adapter = object : RecyclerView.Adapter() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { return ViewHolder( LayoutInflater.from(this@ViewPagerActivity1).inflate( R.layout.pager_item_text, parent, false ) ) } override fun getItemCount() = 5 override fun onBindViewHolder(holder: ViewHolder, position: Int) { val binding = DataBindingUtil.bind(holder.root) ?.apply { text = "page$position" } } } RecyclerView.Adapterを使用する例 ● RecyclerView使用時と同じようなAdapterを 作ればOK ● 各ページはRecyclerView.ViewHolderによっ て提供されるView

Slide 9

Slide 9 text

レシピ名 ページ側Layoutを作成する際の注意点 ● ページ用に指定するLayoutを指定する場合、 ルートViewのサイズはwidth, heightともに match_parentを指定する ● そうしないとIllegalStateExceptionで死ぬ

Slide 10

Slide 10 text

レシピ名 viewPager.orientation = ViewPager2.ORIENTATION_VERTICAL 縦方向にスクロールする ● ページ用に指定するLayoutを指定する場合、 ルートViewのサイズはwidth, heightともに match_parentを指定する コードで指定 XMLで指定

Slide 11

Slide 11 text

レシピ名 viewPager.adapter = object : FragmentStateAdapter(supportFragmentManager) { override fun getItem(position: Int) = PageFragment.newInstance("page$position") override fun getItemCount() = 5 } … class PageFragment : Fragment() { … override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?) = DataBindingUtil.inflate(inflater, R.layout.pager_item_text, container, false).apply { text = arguments?.getString(ARG_TEXT) }.root } } FragmentStateAdapterを使用する例 ● Fragmentを使用してページを作成する場合は FragmentStateAdapterを使用する ● 現時点ではOffscreenPageLimitの仕組みが 存在しないため、隣接するページのFragment の生成は実際のスクロール開始時に行われる ● 生成済みのページに関してはRecyclerViewで いくつかキャッシュされるが、 RecyclerView.setItemViewCacheSize()や、 LinearLayoutManager.getExtraLayoutSpace ()のOverrideでキャッシュ数を調整したりは出 来ない

Slide 12

Slide 12 text

いまのところ出来ない事

Slide 13

Slide 13 text

いまのところ未実装 ● no offscreen limit control ● needs better TabLayout integration ● no pageWidth setter (forced 100%/100%) ● page transformer: no hardware/software layer choice; no reverse drawing order https://developer.android.com/jetpack/androidx/releases/viewpager2 ※もうちょい色々書いてあります。 これ

Slide 14

Slide 14 text

Groupie使える?

Slide 15

Slide 15 text

レシピ名 viewPager.adapter = GroupAdapter().apply { add(TextItem("page1")) add(TextItem("page2")) add(TextItem("page3")) add(TextItem("page4")) add(TextItem("page5")) } } private class TextItem( private val text: String ) : BindableItem() { override fun getLayout() = R.layout.pager_item_text override fun bind(viewBinding: PagerItemTextBinding, position: Int) { viewBinding.text = text } } 単純な使用例 ● GroupieのGroupAdapterは RecyclerView.Adapterを継承しているので普 通に使える ● 自前でAdapterを実装するよりは少し楽かもし れない。

Slide 16

Slide 16 text

レシピ名 viewPager.adapter = GroupAdapter().apply { add(TextItem("page1")) add(ImageItem("world_press17.jpg")) add(TextItem("page3")) add(ImageItem("181126jpower.jpg")) add(TextItem("page5")) add(ImageItem("img_473_1488875864.jpg")) } 単純な使用例2 ● 当然、異なる形のページをGroupieのItemとし て表現する事も可能 ● ViewTypeが複数ある場合は自前でAdapter 作るよりもだいぶ便利かもしれない

Slide 17

Slide 17 text

レシピ名 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) … groupLayoutManager = GridLayoutManager(this, groupAdapter.spanCount).apply { spanSizeLookup = groupAdapter.spanSizeLookup } ) … recyclerView.apply { layoutManager = groupLayoutManager } グリッド表示 ● LayoutManagerがLinerLayoutManagerなの で実現できません!!!! Groupieのサンプルコードから これ

Slide 18

Slide 18 text

レシピ名 viewPager.adapter = GroupAdapter().apply { add(Section().apply { setHeader(TextItem("header")) setFooter(TextItem("footer")) add(TextItem("page1")) add(TextItem("page2")) add(TextItem("page3")) }) } セクションヘッダー、フッター付き ● 使えるけども、 Header→Item→Item→Item→Footerと表示 されるだけなので、まぁ、なんか使えるかもしれ ない…

Slide 19

Slide 19 text

感想 ● 基本的には実績のあるRecyclerViewの実装に依存するので、alphaだけど安定性 は高いと思う ● 旧ViewPagerに実装されていたpageWidthなどの設定が出来ないので、ちょっと凝っ たレイアウトの場合はまだ旧ViewPagerを使うほうがいい ● Offscreen Page Limitの仕組み、キャッシュなどの動きを考えるとページの構成物 がImageViewだけみたいな場合にはViewPager2、Fragment内に複雑なレイアウトを 必要とする場合はViewPagerが良いのではないかな。いまのところ。