working viewpager tablayout showing not multiple fragments example con android android-viewpager fragmentpageradapter

android - tablayout - viewpager con fragments



reordenar las páginas en FragmentStatePagerAdapter usando getItemPosition(objeto Object) (3)

En cuanto a la fuente de FragmentStatePagerAdapter, me di cuenta exactamente de lo que está mal. El FragmentStatePagerAdapter guarda en caché los fragmentos y estados guardados en ArrayLists: mFragments y mSavedState . Pero cuando los fragmentos se reordenan, no hay ningún mecanismo para reordenar los elementos de mFragments y mSavedState . Por lo tanto, el adaptador proporcionará los fragmentos incorrectos al buscapersonas.

He presentado un problema para esto y adjunto una implementación fija (NewFragmentStatePagerAdapter.java) al problema. En la corrección, he agregado una función getItemId() a FragmentStatePagerAdapter. (Esto refleja la implementación de la reordenación en FragmentPagerAdapter.) Se almacena una matriz de itemIds por posición de adaptador en todo momento. Luego, en notifyDataSetChanged() , el adaptador verifica si la matriz de itemIds ha cambiado. Si es así, entonces mFragments y mSavedState se reordenan en consecuencia. Otras modificaciones se pueden encontrar en destroyItem() , saveState() y restoreState() .

Para usar esta clase, getItemPosition() y getItemId() deben implementarse de manera coherente con getItem() .

Creo que FragmentStatePagerAdapter no se comporta correctamente cuando se reemplaza getItemPosition(Object object) con el propósito de reordenar las páginas.

A continuación se muestra un ejemplo simple. En el estado inicial, el orden de las páginas es {A, B, C}. Al llamar a toggleState() , el orden de las páginas cambia a {A, C, B}. Al invalidar getItemPosition(Object object) , nos aseguramos de que la página que se está viendo (A, B o C) no cambie.

public static class TestPagerAdapter extends FragmentStatePagerAdapter { private boolean mState = true; public TestPagerAdapter(FragmentManager fragmentManager) { super(fragmentManager); } @Override public int getCount() { return 3; } private void toggleState() { mState = !mState; notifyDataSetChanged(); } private String getLabel(int position) { switch (position) { case 0: return "A"; case 1: return mState ? "B" : "C"; default: return mState ? "C" : "B"; } } @Override public int getItemPosition(Object object) { String label = ((TestFragment) object).getLabel(); if (label.equals("A")) { return 0; } else if (label.equals("B")) { return mState ? 1 : 2; } else { return mState ? 2 : 1; } } @Override public CharSequence getPageTitle(int position) { return getLabel(position); } @Override public Fragment getItem(int position) { return TestFragment.newInstance(getLabel(position)); } }

Me he encontrado con dos comportamientos separados que parecen incorrectos.

  1. Si llamo inmediatamente a toggleState() (mientras visualizo la página A, antes de pasar a otra página), la aplicación falla.

    java.lang.IndexOutOfBoundsException: Invalid index 2, size is 2 at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:251) at java.util.ArrayList.set(ArrayList.java:477) at android.support.v4.app.FragmentStatePagerAdapter.destroyItem(FragmentStatePagerAdapter.java:136) at android.support.v4.view.ViewPager.populate(ViewPager.java:867) at android.support.v4.view.ViewPager.setCurrentItemInternal(ViewPager.java:469) at android.support.v4.view.ViewPager.setCurrentItemInternal(ViewPager.java:441) at android.support.v4.view.ViewPager.dataSetChanged(ViewPager.java:766) at android.support.v4.view.ViewPager$PagerObserver.onChanged(ViewPager.java:2519) at android.database.DataSetObservable.notifyChanged(DataSetObservable.java:37) at android.support.v4.view.PagerAdapter.notifyDataSetChanged(PagerAdapter.java:276) at com.ugglynoodle.test.testfragmentstatepageradapter.MainActivity$TestPagerAdapter.toggleState(MainActivity.java:55) ...

    En cuanto a la fuente de FragmentStatePagerAdapter , esto se solucionaría al verificar primero el tamaño de mFragments (como en las líneas 113-115) antes de llamar a set() en la línea 136.

  2. Si paso primero a la página B, se getItem(2) , se crea la página C y mFragments ahora tiene un tamaño de 3 (esto evitará que el bloqueo anterior ocurra en un momento). Luego vuelvo a la página A, y la página C se destruye, como debería ser (ya que faltan 2 páginas, y estoy usando el límite de páginas predeterminado fuera de la pantalla de 1). Ahora, llamo toggleState() . La página B ahora está destruida. Sin embargo, la página C NO es recreada! Esto significa que cuando paso a la derecha, obtengo una página vacía.

En primer lugar, sería bueno saber si estoy en lo cierto y estos son, de hecho, errores o si estoy haciendo algo mal. Si son errores, ¿alguien puede sugerir una solución (aparte de depurar y reconstruir la biblioteca de soporte por mi cuenta)? ¿Seguramente alguien debe haber anulado getItemPosition(Object object) éxito (aparte de configurar todo en POSITION_NONE )?

Estoy usando la revisión actual (10) de la biblioteca de soporte.


Para mi funcionó una de las respuestas de un problema . Respuestas # 20 # 21. Enlace a la solución https://gist.github.com/ypresto/8c13cb88a0973d071a64 . La mejor solución, trabaja para actualizar páginas y también reordenar. Solo en esta solución, el adaptador no lanzó IndexOutOfBoundsExeption al destruir el elemento (en el método destroyItem), que es un error conocido para otras soluciones.


Reimplementé la https://gist.github.com/ypresto/8c13cb88a0973d071a64 en Kotlin de tal manera que le permite devolver una String lugar de una long para la identificación del artículo. Lo puedes encontrar here o abajo:

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.view.View import android.view.ViewGroup import java.util.HashSet import java.util.LinkedHashMap /** * A PagerAdapter that can withstand item reordering. See * https://issuetracker.google.com/issues/36956111. * * @see android.support.v4.app.FragmentStatePagerAdapter */ abstract class MovableFragmentStatePagerAdapter( private val manager: FragmentManager ) : NullablePagerAdapter() { private var currentTransaction: FragmentTransaction? = null private var currentPrimaryItem: Fragment? = null private val savedStates = LinkedHashMap<String, Fragment.SavedState>() private val fragmentsToItemIds = LinkedHashMap<Fragment, String>() private val itemIdsToFragments = LinkedHashMap<String, Fragment>() private val unusedRestoredFragments = HashSet<Fragment>() /** @see android.support.v4.app.FragmentStatePagerAdapter.getItem */ abstract fun getItem(position: Int): Fragment /** * @return a unique identifier for the item at the given position. */ abstract fun getItemId(position: Int): String /** @see android.support.v4.app.FragmentStatePagerAdapter.startUpdate */ override fun startUpdate(container: ViewGroup) { check(container.id != View.NO_ID) { "ViewPager with adapter $this requires a view id." } } /** @see android.support.v4.app.FragmentStatePagerAdapter.instantiateItem */ override fun instantiateItem(container: ViewGroup, position: Int): Any { val itemId = getItemId(position) val f = itemIdsToFragments[itemId] if (f != null) { unusedRestoredFragments.remove(f) return f } if (currentTransaction == null) { // We commit the transaction later @SuppressLint("CommitTransaction") currentTransaction = manager.beginTransaction() } val fragment = getItem(position) fragmentsToItemIds.put(fragment, itemId) itemIdsToFragments.put(itemId, fragment) val fss = savedStates[itemId] if (fss != null) { fragment.setInitialSavedState(fss) } fragment.setMenuVisibility(false) fragment.userVisibleHint = false currentTransaction!!.add(container.id, fragment) return fragment } /** @see android.support.v4.app.FragmentStatePagerAdapter.destroyItem */ override fun destroyItem(container: ViewGroup, position: Int, fragment: Any) { (fragment as Fragment).destroy() } /** @see android.support.v4.app.FragmentStatePagerAdapter.setPrimaryItem */ override fun setPrimaryItem(container: ViewGroup, position: Int, fragment: Any?) { fragment as Fragment? if (fragment !== currentPrimaryItem) { currentPrimaryItem?.let { it.setMenuVisibility(false) it.userVisibleHint = false } fragment?.setMenuVisibility(true) fragment?.userVisibleHint = true currentPrimaryItem = fragment } } /** @see android.support.v4.app.FragmentStatePagerAdapter.finishUpdate */ override fun finishUpdate(container: ViewGroup) { if (!unusedRestoredFragments.isEmpty()) { for (fragment in unusedRestoredFragments) fragment.destroy() unusedRestoredFragments.clear() } currentTransaction?.let { it.commitAllowingStateLoss() currentTransaction = null manager.executePendingTransactions() } } /** @see android.support.v4.app.FragmentStatePagerAdapter.isViewFromObject */ override fun isViewFromObject(view: View, fragment: Any): Boolean = (fragment as Fragment).view === view /** @see android.support.v4.app.FragmentStatePagerAdapter.saveState */ override fun saveState(): Parcelable? = Bundle().apply { putStringArrayList(KEY_FRAGMENT_IDS, ArrayList<String>(savedStates.keys)) putParcelableArrayList( KEY_FRAGMENT_STATES, ArrayList<Fragment.SavedState>(savedStates.values) ) for ((f, id) in fragmentsToItemIds.entries) { if (f.isAdded) { manager.putFragment(this, "$KEY_FRAGMENT_STATE$id", f) } } } /** @see android.support.v4.app.FragmentStatePagerAdapter.restoreState */ override fun restoreState(state: Parcelable?, loader: ClassLoader?) { if ((state as Bundle?)?.apply { classLoader = loader }?.isEmpty == false) { state!! fragmentsToItemIds.clear() itemIdsToFragments.clear() unusedRestoredFragments.clear() savedStates.clear() val fragmentIds: List<String> = state.getStringArrayList(KEY_FRAGMENT_IDS) val fragmentStates: List<Fragment.SavedState> = state.getParcelableArrayList(KEY_FRAGMENT_STATES) for ((index, id) in fragmentIds.withIndex()) { savedStates.put(id, fragmentStates[index]) } for (key: String in state.keySet()) { if (key.startsWith(KEY_FRAGMENT_STATE)) { val itemId = key.substring(KEY_FRAGMENT_STATE.length) manager.getFragment(state, key)?.let { it.setMenuVisibility(false) fragmentsToItemIds.put(it, itemId) itemIdsToFragments.put(itemId, it) } } } unusedRestoredFragments.addAll(fragmentsToItemIds.keys) } } private fun Fragment.destroy() { if (currentTransaction == null) { // We commit the transaction later @SuppressLint("CommitTransaction") currentTransaction = manager.beginTransaction() } val itemId = fragmentsToItemIds.remove(this) itemIdsToFragments.remove(itemId) if (itemId != null) { savedStates.put(itemId, manager.saveFragmentInstanceState(this)) } currentTransaction!!.remove(this) } private companion object { const val KEY_FRAGMENT_IDS = "fragment_keys_" const val KEY_FRAGMENT_STATES = "fragment_states_" const val KEY_FRAGMENT_STATE = "fragment_state_" } }

Y la pieza de Java:

import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.view.PagerAdapter; import android.view.ViewGroup; /** * A PagerAdapter whose {@link #setPrimaryItem} is overridden with proper nullability annotations. */ public abstract class NullablePagerAdapter extends PagerAdapter { @Override public void setPrimaryItem(@NonNull ViewGroup container, int position, @Nullable Object object) { // `object` is actually nullable. It''s even in the dang source code which is hilariously // ridiculous: // `mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);` } }