Skip to content

Instantly share code, notes, and snippets.

@nakamuuu
Last active December 21, 2019 07:08
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nakamuuu/6d93ebc9ba2c14a805c4f6770db4f46c to your computer and use it in GitHub Desktop.
Save nakamuuu/6d93ebc9ba2c14a805c4f6770db4f46c to your computer and use it in GitHub Desktop.
InfiniteFragmentStatePagerAdapter
import android.annotation.SuppressLint
import android.os.Bundle
import android.os.Parcelable
import android.support.v4.app.Fragment
import android.support.v4.app.FragmentManager
import android.support.v4.app.FragmentTransaction
import android.support.v4.view.PagerAdapter
import android.view.View
import android.view.ViewGroup
abstract class InfiniteFragmentStatePagerAdapter(
private val fragmentManager: FragmentManager
) : PagerAdapter() {
companion object {
private const val KEY_FRAGMENT_STATES = "states"
}
private var currentTransaction: FragmentTransaction? = null
private var currentPrimaryItem: Fragment? = null
private val savedStates = HashMap<String, Fragment.SavedState>()
private val fragments = HashMap<String, Fragment>()
abstract fun getItemCount(): Int
abstract fun getItem(position: Int): Fragment
abstract fun getItemIdentifier(position: Int): String
abstract fun getItemTitle(position: Int): String
@SuppressLint("CommitTransaction")
override final fun instantiateItem(container: ViewGroup, position: Int): Any {
val identifier = getItemIdentifier(getInternalItemPosition(position))
fragments[identifier]?.let { return it }
if (currentTransaction == null) {
currentTransaction = fragmentManager.beginTransaction()
}
val fragment = getItem(getInternalItemPosition(position)).apply {
savedStates[identifier]?.let { setInitialSavedState(it) }
setMenuVisibility(false)
userVisibleHint = false
}
fragments[identifier] = fragment
currentTransaction?.add(container.id, fragment)
return fragment
}
@SuppressLint("CommitTransaction")
override final fun destroyItem(container: ViewGroup, position: Int, obj: Any) {
val identifier = getItemIdentifier(getInternalItemPosition(position))
val fragment = obj as Fragment
if (currentTransaction == null) {
currentTransaction = fragmentManager.beginTransaction()
}
fragments.remove(identifier)
if (fragment.isAdded) {
savedStates[identifier] = fragmentManager.saveFragmentInstanceState(fragment)
} else {
savedStates.remove(identifier)
}
currentTransaction?.remove(fragment)
}
override final fun setPrimaryItem(container: ViewGroup, position: Int, obj: Any?) {
val fragment = obj as? Fragment
if (fragment !== currentPrimaryItem) {
currentPrimaryItem?.run {
setMenuVisibility(false)
userVisibleHint = false
}
currentPrimaryItem = fragment?.apply {
setMenuVisibility(true)
userVisibleHint = true
}
}
}
override final fun finishUpdate(container: ViewGroup) {
currentTransaction?.commitNowAllowingStateLoss()
currentTransaction = null
}
override final fun isViewFromObject(view: View, obj: Any) = (obj as Fragment).view === view
override final fun saveState(): Parcelable {
val state = Bundle()
if (savedStates.isNotEmpty()) {
state.putSerializable(KEY_FRAGMENT_STATES, savedStates)
}
fragments.keys.forEach { identifier ->
fragments[identifier]?.takeIf { it.isAdded }?.let {
fragmentManager.putFragment(state, identifier, it)
}
}
return state
}
override final fun restoreState(state: Parcelable?, loader: ClassLoader?) {
val bundle = (state as? Bundle)?.apply { classLoader = loader } ?: return
fragments.clear()
savedStates.clear()
@Suppress("UNCHECKED_CAST")
savedStates.putAll((bundle.getSerializable(KEY_FRAGMENT_STATES) as HashMap<String, Fragment.SavedState>))
savedStates.keys.forEach { identifier ->
fragmentManager.getFragment(bundle, identifier)?.let {
it.setMenuVisibility(false)
fragments[identifier] = it
}
}
}
// アイテムが変更された際に呼ばれ、移動後のアイテムの位置を返すメソッド。
// 常時 POSITION_NONE を返した場合は更新時にすべてのFragmentが再生成されることになるため、
// アイテムの動的な変更が頻繁に発生する場合は適切な位置を返し、かつ不要なSaveStateを破棄することが望ましい。
override final fun getItemPosition(obj: Any?) = POSITION_NONE
override final fun getCount() = if (getItemCount() == 0) 0 else Integer.MAX_VALUE
override final fun getPageTitle(position: Int) = getItemTitle(getInternalItemPosition(position))
// この値で ViewPager#setCurrentItem(int, boolean) を呼び出して、初期位置がちょうど真ん中になるようにする
fun getInitialPosition() = (count.toDouble() / 2 / getItemCount()).toInt() * getItemCount()
private fun getInternalItemPosition(position: Int) = if (getItemCount() == 0) 0 else position % getItemCount()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment