Last active
December 21, 2019 07:08
-
-
Save nakamuuu/6d93ebc9ba2c14a805c4f6770db4f46c to your computer and use it in GitHub Desktop.
InfiniteFragmentStatePagerAdapter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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