以下は androidx.fragment:fragment:1.0.0 での挙動です。1.1.0-alpha07 で別の挙動にするためのオプションが追加されています。
ViewPager + FragmentPagerAdapter の構成はよく使うと思います。
FragmentPagerAdapter では、ViewPager のページが切り替わる処理の中で、
以前の PrimaryItem の Fragment に対して setUserVisibleHint(false) を呼び、
新しい PrimaryItem の Fragment に対して setUserVisibleHint(true) を呼んでいます。
挙動を確認するために、FragmentPagerAdapter の getItem() で返す Fragment に次のようにログを入れました。
- class SimpleFragment : Fragment() {
-
- ...
-
- override fun setUserVisibleHint(isVisibleToUser: Boolean) {
- println("$position : setUserVisibleHint : before super : to = $isVisibleToUser, current = $userVisibleHint")
- super.setUserVisibleHint(isVisibleToUser)
- println("$position : setUserVisibleHint : after super : to = $isVisibleToUser, current = $userVisibleHint")
- }
-
- override fun onStart() {
- super.onStart()
- println("$position : onStart : $userVisibleHint")
- }
- }
class SimpleFragment : Fragment() {
...
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
println("$position : setUserVisibleHint : before super : to = $isVisibleToUser, current = $userVisibleHint")
super.setUserVisibleHint(isVisibleToUser)
println("$position : setUserVisibleHint : after super : to = $isVisibleToUser, current = $userVisibleHint")
}
override fun onStart() {
super.onStart()
println("$position : onStart : $userVisibleHint")
}
}
1. レイアウトより前のタイミング(例えば onCreate())で adapter をセットしている場合
- class MainActivity : AppCompatActivity() {
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_main)
-
- viewPager.adapter = MyPager(supportFragmentManager)
- }
- }
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewPager.adapter = MyPager(supportFragmentManager)
}
}
この場合、ログは次のようになります。
- System.out: 0 : setUserVisibleHint : before super : to = false, current = true
- System.out: 0 : setUserVisibleHint : after super : to = false, current = false
-
- System.out: 1 : setUserVisibleHint : before super : to = false, current = true
- System.out: 1 : setUserVisibleHint : after super : to = false, current = false
-
- System.out: 0 : setUserVisibleHint : before super : to = true, current = false
- System.out: 0 : setUserVisibleHint : after super : to = true, current = true
-
- System.out: 0 : onStart : true
- System.out: 1 : onStart : false
System.out: 0 : setUserVisibleHint : before super : to = false, current = true
System.out: 0 : setUserVisibleHint : after super : to = false, current = false
System.out: 1 : setUserVisibleHint : before super : to = false, current = true
System.out: 1 : setUserVisibleHint : after super : to = false, current = false
System.out: 0 : setUserVisibleHint : before super : to = true, current = false
System.out: 0 : setUserVisibleHint : after super : to = true, current = true
System.out: 0 : onStart : true
System.out: 1 : onStart : false
まず position 0 と 1 の Fragment の setUserVisibleHint が false にセットされます。
super.setUserVisibleHint() が呼ばれる前の時点で取得した userVisibleHint が true ですね。
実は Fragment の mUserVisibleHint はデフォルトが true です。
- package androidx.fragment.app;
-
- ...
-
- public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener, LifecycleOwner,
- ViewModelStoreOwner {
-
- ...
-
-
- boolean mUserVisibleHint = true;
-
- ...
- }
package androidx.fragment.app;
...
public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener, LifecycleOwner,
ViewModelStoreOwner {
...
// Hint provided by the app that this fragment is currently visible to the user.
boolean mUserVisibleHint = true;
...
}
次に position 0 の Fragment の setUserVisibleHint が true にセットされます。
その後、Fragment の onStart() が呼ばれます。
つまり
- 1. position 0 の Fragment の setUserVisibleHint() : true → false
- 2. position 1 の Fragment の setUserVisibleHint() : true → false
- 3. position 0 の Fragment の setUserVisibleHint() : false → true
- 4. position 0 の Fragment の onStart() : setUserVisibleHint は true
- 5. position 1 の Fragment の onStart() : setUserVisibleHint は false
という流れです。
2. レイアウトより前のタイミング(例えば onCreate())で adapter をセットし、currentItem を変更している場合
onCreate() で ViewPager に adapter をセットした後 currentItem を 1 にしてみます。
- class MainActivity : AppCompatActivity() {
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_main)
-
- viewPager.adapter = MyPager(supportFragmentManager)
- viewPager.currentItem = 1
- }
- }
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewPager.adapter = MyPager(supportFragmentManager)
viewPager.currentItem = 1
}
}
この場合のログはこうなります。
- System.out: 1 : setUserVisibleHint : before super : to = false, current = true
- System.out: 1 : setUserVisibleHint : after super : to = false, current = false
-
- System.out: 0 : setUserVisibleHint : before super : to = false, current = true
- System.out: 0 : setUserVisibleHint : after super : to = false, current = false
-
- System.out: 2 : setUserVisibleHint : before super : to = false, current = true
- System.out: 2 : setUserVisibleHint : after super : to = false, current = false
-
- System.out: 1 : setUserVisibleHint : before super : to = true, current = false
- System.out: 1 : setUserVisibleHint : after super : to = true, current = true
-
- System.out: 1 : onStart : true
- System.out: 0 : onStart : false
- System.out: 2 : onStart : false
System.out: 1 : setUserVisibleHint : before super : to = false, current = true
System.out: 1 : setUserVisibleHint : after super : to = false, current = false
System.out: 0 : setUserVisibleHint : before super : to = false, current = true
System.out: 0 : setUserVisibleHint : after super : to = false, current = false
System.out: 2 : setUserVisibleHint : before super : to = false, current = true
System.out: 2 : setUserVisibleHint : after super : to = false, current = false
System.out: 1 : setUserVisibleHint : before super : to = true, current = false
System.out: 1 : setUserVisibleHint : after super : to = true, current = true
System.out: 1 : onStart : true
System.out: 0 : onStart : false
System.out: 2 : onStart : false
まず position 1, 0, 2 の Fragment の setUserVisibleHint が false にセットされ、
次に position 1 の Fragment の setUserVisibleHint が true にセットされ、
その後、Fragment の onStart() が呼ばれます。
つまり
- 1. position 1 の Fragment の setUserVisibleHint() : true → false
- 2. position 0 の Fragment の setUserVisibleHint() : true → false
- 3. position 2 の Fragment の setUserVisibleHint() : true → false
- 4. position 1 の Fragment の setUserVisibleHint() : false → true
- 5. position 1 の Fragment の onStart() : setUserVisibleHint は true
- 6. position 0 の Fragment の onStart() : setUserVisibleHint は false
- 7. position 2 の Fragment の onStart() : setUserVisibleHint は false
という流れです。
3. レイアウトより後のタイミングで adapter をセットしている場合
- class MainActivity : AppCompatActivity() {
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_main)
-
- Handler().postDelayed(
- {
- viewPager.adapter = MyPager(supportFragmentManager)
- },
- 1000
- )
- }
- }
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Handler().postDelayed(
{
viewPager.adapter = MyPager(supportFragmentManager)
},
1000
)
}
}
onCreate() から1秒後に adapter をセットしてみます。この場合ログは次のようになります。
- System.out: 0 : setUserVisibleHint : before super : to = false, current = true
- System.out: 0 : setUserVisibleHint : after super : to = false, current = false
-
- System.out: 1 : setUserVisibleHint : before super : to = false, current = true
- System.out: 1 : setUserVisibleHint : after super : to = false, current = false
-
- System.out: 0 : setUserVisibleHint : before super : to = true, current = false
- System.out: 0 : setUserVisibleHint : after super : to = true, current = true
-
- System.out: 0 : onStart : true
- System.out: 1 : onStart : false
System.out: 0 : setUserVisibleHint : before super : to = false, current = true
System.out: 0 : setUserVisibleHint : after super : to = false, current = false
System.out: 1 : setUserVisibleHint : before super : to = false, current = true
System.out: 1 : setUserVisibleHint : after super : to = false, current = false
System.out: 0 : setUserVisibleHint : before super : to = true, current = false
System.out: 0 : setUserVisibleHint : after super : to = true, current = true
System.out: 0 : onStart : true
System.out: 1 : onStart : false
「1. レイアウトより前のタイミング(例えば onCreate())で adapter をセットしている場合」と同じですね。
- 1. position 0 の Fragment の setUserVisibleHint() : true → false
- 2. position 1 の Fragment の setUserVisibleHint() : true → false
- 3. position 0 の Fragment の setUserVisibleHint() : false → true
- 4. position 0 の Fragment の onStart() : setUserVisibleHint は true
- 5. position 1 の Fragment の onStart() : setUserVisibleHint は false
という流れです。
4. レイアウトより後のタイミングで adapter をセットし、currentItem を変更している場合
onCreate() から1秒後に adapter をセットし、currentItem を 1 にしてみます。
- class MainActivity : AppCompatActivity() {
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_main)
-
- Handler().postDelayed(
- {
- viewPager.adapter = MyPager(supportFragmentManager)
- viewPager.currentItem = 1
- },
- 1000
- )
- }
- }
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Handler().postDelayed(
{
viewPager.adapter = MyPager(supportFragmentManager)
viewPager.currentItem = 1
},
1000
)
}
}
この場合ログは次のようになります。
- System.out: 0 : setUserVisibleHint : before super : to = false, current = true
- System.out: 0 : setUserVisibleHint : after super : to = false, current = false
-
- System.out: 1 : setUserVisibleHint : before super : to = false, current = true
- System.out: 1 : setUserVisibleHint : after super : to = false, current = false
-
- System.out: 0 : setUserVisibleHint : before super : to = true, current = false
- System.out: 0 : setUserVisibleHint : after super : to = true, current = true
-
- System.out: 0 : onStart : true
- System.out: 1 : onStart : false
-
- System.out: 2 : setUserVisibleHint : before super : to = false, current = true
- System.out: 2 : setUserVisibleHint : after super : to = false, current = false
-
- System.out: 0 : setUserVisibleHint : before super : to = false, current = true
- System.out: 0 : setUserVisibleHint : after super : to = false, current = false
-
- System.out: 1 : setUserVisibleHint : before super : to = true, current = false
- System.out: 1 : setUserVisibleHint : after super : to = true, current = true
-
- System.out: 2 : onStart : false
System.out: 0 : setUserVisibleHint : before super : to = false, current = true
System.out: 0 : setUserVisibleHint : after super : to = false, current = false
System.out: 1 : setUserVisibleHint : before super : to = false, current = true
System.out: 1 : setUserVisibleHint : after super : to = false, current = false
System.out: 0 : setUserVisibleHint : before super : to = true, current = false
System.out: 0 : setUserVisibleHint : after super : to = true, current = true
System.out: 0 : onStart : true
System.out: 1 : onStart : false
System.out: 2 : setUserVisibleHint : before super : to = false, current = true
System.out: 2 : setUserVisibleHint : after super : to = false, current = false
System.out: 0 : setUserVisibleHint : before super : to = false, current = true
System.out: 0 : setUserVisibleHint : after super : to = false, current = false
System.out: 1 : setUserVisibleHint : before super : to = true, current = false
System.out: 1 : setUserVisibleHint : after super : to = true, current = true
System.out: 2 : onStart : false
「2. レイアウトより前のタイミング(例えば onCreate())で adapter をセットし、currentItem を変更している場合」と同じになりません!
まず「3. レイアウトより後のタイミングで adapter をセットしている場合」と同じことが起こります。
つまり
- 1. position 0 の Fragment の setUserVisibleHint() : true → false
- 2. position 1 の Fragment の setUserVisibleHint() : true → false
- 3. position 0 の Fragment の setUserVisibleHint() : false → true
- 4. position 0 の Fragment の onStart() : setUserVisibleHint は true
- 5. position 1 の Fragment の onStart() : setUserVisibleHint は false
という流れが最初にあります。
その後
- 6. position 2 の Fragment の setUserVisibleHint() : true → false
- 7. position 0 の Fragment の setUserVisibleHint() : true → false
- 8. position 1 の Fragment の setUserVisibleHint() : false → true
- 9. position 2 の Fragment の onStart() : setUserVisibleHint は false
という流れになります。
adapter をセットしたタイミングで 1 〜 5 までの流れが起こり、currentItem を 1 にしたタイミングで 6 〜 9 の流れが起こります。
しかも position 1 が表示されているのに、onStart() 時の setUserVisibleHint 値を見ると position 0 が表示されているかのようです。
このように、レイアウトよりも後のタイミングで adapter をセットすると、その時点で position 0 の Fragment を PrimaryItem として onStart() まで呼ばれてしまいます。
そのため、レイアウトよりも後のタイミングで adapter をセットし、currentItem を 0 以外に変更する場合は setUserVisibleHint() や onStart() が呼ばれるタイミングに注意が必要です。