0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

FlutterFragmentとViewPagerの組み合わせはonResumeのタイミングで難しかった

Last updated at Posted at 2020-07-28

ViewPager2を使って2ページ目以降FlutterFragmentを設定すると、完全にページ遷移するまではFlutterFragmentが描画されず真っ白になります。一度表示すると真っ白になりません。

device-2020-07-28-223018.gif

このように遷移途中でもFlutterFragmentが描画されて欲しいです。

device-2020-07-29-065552.gif

なぜそうなるか

作り方

ViewPager2のAdapterはこのように作りました。

MyFragmentStateAdapter.kt
class MyFragmentStateAdapter(activity: FragmentActivity) : FragmentStateAdapter(activity) {
    override fun getItemCount() = 2

    override fun createFragment(position: Int): Fragment {
        return if (position == 0)
        // AndroidネイティブなFragment
            MainFragment()
        else
        // FlutterFragment
            FlutterFragment.withNewEngine()
                    .shouldAttachEngineToActivity(false)
                    .build()
    }
}

ViewPager2はこのように設定しました。

PagerActivity.kt
viewPager.adapter = MyFragmentStateAdapter(this)
// こちらを設定しないとスワイプ開始時に一瞬ひっかかる
viewPager.offscreenPageLimit = 1

Fragmentのライフサイクルを確認する

2ページ目をFlutterFragmentから空のフラグメントにします。

MyFragmentStateAdapter.kt
class MyFragmentStateAdapter(activity: FragmentActivity) : FragmentStateAdapter(activity) {
    override fun getItemCount() = 2

    override fun createFragment(position: Int): Fragment {
        return if (position == 0)
            MainFragment()
        else
            EmptyFragment()
    }
}

空のフラグメントのライフサイクルを確認します。

EmptyFragment.kt
class EmptyFragment : Fragment() {
    override fun onStart() {
        super.onStart()
        Log.d("EmptyFragment", "onStart")
    }

    override fun onResume() {
        super.onResume()
        Log.d("EmptyFragment", "onResume")
    }

    override fun onPause() {
        super.onPause()
        Log.d("EmptyFragment", "onPause")
    }

    override fun onStop() {
        super.onStop()
        Log.d("EmptyFragment", "onStop")
    }
}

logcatを確認した所、onStartは画面を開いたときに呼ばれますが、onResumeは2ページ目に完全遷移するまで呼ばれませんでした。

# 画面を開いた
D EmptyFragment: onStart
# 2ページ目に完全に遷移
D EmptyFragment: onResume

どうやらFlutterFragmentは初回のonResumeで描画されるようです。この挙動を変更する方法は見つけられませんでした。

ViewPager2ではなくViewPagerを使った場合

コンストラクタのbehaviorフィールドにBEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT定数を設定した場合はViewPager2と同じくonResumeは2ページ目に完全遷移するまで呼ばれずFlutterFragmentの描画も完全遷移まで開始されません。

MyFragmentStateAdapter.kt
class MyFragmentStateAdapter(fm: FragmentManager) :
        FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {

    override fun getCount() = 2

    override fun getItem(position: Int): Fragment {
        return if (position == 0)
            MainFragment()
        else
            FlutterFragment.withNewEngine()
                    .shouldAttachEngineToActivity(false)
                    .build()
    }
}

behaviorフィールドをBEHAVIOR_SET_USER_VISIBLE_HINT定数にすれば、画面が開かれると同時にonResumeが呼ばれるので、FlutterFragmentの描画もスワイプ中に行われます。しかしその定数は非推奨扱いなので、後ほど技術的負債になる危険性を感じました。

onResumeが完全遷移まで呼ばれない理由

非推奨になったBEHAVIOR_SET_USER_VISIBLE_HINT定数について調べてみたところ、setMaxLifecycleメソッドが紹介されていて、それについて調べてみたところ、こちらの記事に行き着きました。

setUserVisibleHintのdeprecatedとsetMaxLifecycle

この記事の情報を持ってViewPager, ViewPager2のアダプターのソースコードを見てみたところsetMaxLifecycleメソッドの呼び出しで、完全遷移するまではonStartまでしかライフサイクルを遷移させない設定にしていたようです。どうしてもViewPagerを使いたい場合は、ライブラリをフォークしてsetMaxLifecycle呼び出し部分を書き換える必要がありそうです。

最終的にはUIデザインを変更しました

結局、横スワイプによるページ切り替えは諦めて、ボタンによる画面呼び出しにしました。

device-2020-07-29-070749.gif

今回に関してはそちらの方がUIとしては分かりやすいと思いました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?