¿Cómo manejar correctamente la rotación de la pantalla con un ViewPager y fragmentos anidados?

Tengo esta actividad, que contiene un fragmento. Este diseño de fragmento consiste en un buscapersonas de vista con varios fragmentos (dos, en realidad).

Cuando se crea el paginador de vista, se crea su adaptador, getItem se llama y se crean mis getItem . Estupendo.

Ahora cuando rota la pantalla, el marco maneja el fragmento de recreación, el adaptador se crea de nuevo en mi onCreate del fragmento principal, pero getItem nunca se llama , por lo que mi adaptador contiene referencias incorrectas (en realidad nulos) en lugar de los dos fragmentos .

Lo que he encontrado es que el gestor de fragmentos (es decir, el gestor de fragmentos secundarios) contiene una matriz de fragmentos llamada mActive , que por supuesto no es accesible desde el código. Sin embargo, hay este método getFragment :

 @Override public Fragment getFragment(Bundle bundle, String key) { int index = bundle.getInt(key, -1); if (index == -1) { return null; } if (index >= mActive.size()) { throwException(new IllegalStateException("Fragement no longer exists for key " + key + ": index " + index)); } Fragment f = mActive.get(index); if (f == null) { throwException(new IllegalStateException("Fragement no longer exists for key " + key + ": index " + index)); } return f; } 

No voy a comentar el error tipográfico 🙂
Este es el hack que he implementado para actualizar las referencias a mis fragmentos, en mi constructor de adaptador:

 // fm holds a reference to a FragmentManager Bundle hack = new Bundle(); try { for (int i = 0; i < mFragments.length; i++) { hack.putInt("hack", i); mFragments[i] = fm.getFragment(hack, "hack"); } } catch (Exception e) { // No need to fail here, likely because it's the first creation and mActive is empty } 

No estoy orgulloso. Esto funciona, pero es feo. ¿Cuál es la forma real de tener un adaptador válido después de una rotación de pantalla?

PS: aquí está el código completo

Tuve el mismo problema, supongo que estás subclasificando FragmentPagerAdapter para tu adaptador de paginador (como getItem() es específico de FragmentPagerAdapter ).

Mi solución fue en lugar de subclase PagerAdapter y manejar el fragmento de creación / eliminación usted mismo (reimplementar algunos de los FragmentPagerAdapter código):

 public class ListPagerAdapter extends PagerAdapter { FragmentManager fragmentManager; Fragment[] fragments; public ListPagerAdapter(FragmentManager fm){ fragmentManager = fm; fragments = new Fragment[5]; } @Override public void destroyItem(ViewGroup container, int position, Object object) { assert(0 <= position && position < fragments.length); FragmentTransaction trans = fragmentManager.beginTransaction(); trans.remove(fragments[position]); trans.commit(); fragments[position] = null; } @Override public Fragment instantiateItem(ViewGroup container, int position){ Fragment fragment = getItem(position); FragmentTransaction trans = fragmentManager.beginTransaction(); trans.add(container.getId(),fragment,"fragment:"+position); trans.commit(); return fragment; } @Override public int getCount() { return fragments.length; } @Override public boolean isViewFromObject(View view, Object fragment) { return ((Fragment) fragment).getView() == view; } public Fragment getItem(int position){ assert(0 <= position && position < fragments.length); if(fragments[position] == null){ fragments[position] = ; //make your fragment here } return fragments[position]; } } 

Espero que esto ayude.

Mi respuesta es similar a la de Joshua Hunt, pero al comprometer la transacción en el método finishUpdate se obtiene un rendimiento mucho mejor. Una transacción en lugar de dos por actualización. Aquí está el código:

 private class SuchPagerAdapter extends PagerAdapter{ private final FragmentManager mFragmentManager; private SparseArray<Fragment> mFragments; private FragmentTransaction mCurTransaction; private SuchPagerAdapter(FragmentManager fragmentManager) { mFragmentManager = fragmentManager; mFragments = new SparseArray<>(); } @Override public Object instantiateItem(ViewGroup container, int position) { Fragment fragment = getItem(position); if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } mCurTransaction.add(container.getId(),fragment,"fragment:"+position); return fragment; } @Override public void destroyItem(ViewGroup container, int position, Object object) { if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } mCurTransaction.detach(mFragments.get(position)); mFragments.remove(position); } @Override public boolean isViewFromObject(View view, Object fragment) { return ((Fragment) fragment).getView() == view; } public Fragment getItem(int position) { return YoursVeryFragment.instantiate(); } @Override public void finishUpdate(ViewGroup container) { if (mCurTransaction != null) { mCurTransaction.commitAllowingStateLoss(); mCurTransaction = null; mFragmentManager.executePendingTransactions(); } } @Override public int getCount() { return countOfPages; } } 
 @Override public void onCreate(Bundle savedState) { ... // Retain Fragment instance across Activity re-creation. // onCreate() and onDestroy() will no longer be called on re-creation. // onAttach(), onDetach(), and onActivityCreated() will still be called. setRetainInstance(true); ... } 

Mi código :

  public class SampleAdapter extends FragmentStatePagerAdapter { private Fragment mFragmentAtPos2; private FragmentManager mFragmentManager; private Fragment[] mFragments = new Fragment[3]; public SampleAdapter(FragmentManager mgr) { super(mgr); mFragmentManager = mgr; Bundle hack = new Bundle(); try { for (int i = 0; i < mFragments.length; i++) { hack.putInt("hack", i); mFragments[i] = mFragmentManager.getFragment(hack, "hack"); } } catch (Exception e) { // No need to fail here, likely because it's the first creation and mActive is empty } } public void switchFrag(Fragment frag) { if (frag == null) { Dbg.e(TAG, "- switch(frag) frag is NULL"); return; } else Dbg.v(TAG, "- switch(frag) - frag is " + frag.getClass()); // We have to check for mFragmentAtPos2 null in case of first time (only Mytrips fragment being instatiante). if (mFragmentAtPos2!= null) mFragmentManager.beginTransaction() .remove(mFragmentAtPos2) .commit(); mFragmentAtPos2 = frag; notifyDataSetChanged(); } @Override public int getCount() { return(3); } @Override public int getItemPosition(Object object) { Dbg.v(TAG,"getItemPosition : "+object.getClass()); if (object instanceof MyTripsFragment || object instanceof FindingDriverFragment || object instanceof BookingAcceptedFragment || object instanceof RideStartedFragment || object instanceof RideEndedFragment || object instanceof ContactUsFragment ) return POSITION_NONE; else return POSITION_UNCHANGED; } @Override public Fragment getItem(int position) { Dbg.v("SampleAdapter", "getItem called on: "+position); switch (position) { case 0: if (snapbookFrag==null) { snapbookFrag = new SnapBookingFragment(); Dbg.e(TAG, "snapbookFrag created"); } return snapbookFrag; case 1: if(bookingFormFrag==null) { bookingFormFrag = new BookingFormFragment(); Dbg.e(TAG, "bookingFormFrag created"); } return bookingFormFrag; case 2: if (mFragmentAtPos2 == null) { myTripsFrag = new MyTripsFragment(); mFragmentAtPos2 = myTripsFrag; return mFragmentAtPos2; } return mFragmentAtPos2; default: return(new SnapBookingFragment()); } } } 

Con referencia a la solución de simekadam, mFragments no se está poblando en instantiateItem y necesita mFragments.put(position, fragment); Dentro o terminarás con este error: Tratar de eliminar el fragmento de la vista me da NullPointerException en mNextAnim .

El problema es que getItem() en FragmentPageAdapter tiene un nombre incorrecto. Debe haber sido llamado createItem() . Debido a la forma en que funciona el getItem() es para crear fragmentos y no es seguro llamarlo a la consulta / encontrar un fragmento.

Mi recomendación es hacer una copia de FragmentPagerAdapter actual y cambiarlo de esa manera:

Añadir:

  public abstract Fragment createFragment(int position); 

Y cambie getItem a:

 public Fragment getItem(int position) { if(containerId!=null) { final long itemId = getItemId(position); String name = makeFragmentName(containerId, itemId); return mFragmentManager.findFragmentByTag(name); } else { return null; } } 

Finalmente agregue eso a instantiateItem:

  if(containerId==null) containerId = container.getId(); else if(containerId!=container.getId()) throw new RuntimeException("Container id not expected to change"); 

Código completo en esta esencia

Creo que esta implementación es más segura y más fácil de usar y también tiene el mismo rendimiento del adaptador original de los ingenieros de Google.

¿Por qué soluciones tan complejas? Parece exceso. Lo soluciono sólo con la sustitución de la antigua referencia a nuevo en la clase extendida de FragmentPagerAdapter

  @Override public Object instantiateItem(ViewGroup container, int position) { frags[position] = (Fragment) super.instantiateItem(container, position); return frags[position]; } 

Todo el código del adaptador se parece a esto

 public class RelationsFragmentsAdapter extends FragmentPagerAdapter { private final String titles[] = new String[3]; private final Fragment frags[] = new Fragment[titles.length]; public RelationsFragmentsAdapter(FragmentManager fm) { super(fm); frags[0] = new FriendsFragment(); frags[1] = new FriendsRequestFragment(); frags[2] = new FriendsDeclinedFragment(); Resources resources = AppController.getAppContext().getResources(); titles[0] = resources.getString(R.string.my_friends); titles[1] = resources.getString(R.string.my_new_friends); titles[2] = resources.getString(R.string.followers); } @Override public CharSequence getPageTitle(int position) { return titles[position]; } @Override public Fragment getItem(int position) { return frags[position]; } @Override public int getCount() { return frags.length; } @Override public Object instantiateItem(ViewGroup container, int position) { frags[position] = (Fragment) super.instantiateItem(container, position); return frags[position]; } 

}

  • Cómo cambiar el color del marcador actual de la pestaña en Android ViewPager?
  • Uso de SlidingPaneLayout de Android con ViewPager
  • IllegalStateException: base de datos ya cerrada (con ViewPager)
  • ¿Qué está mal con este código para usar ViewPager?
  • Obtención de la instancia Fragment actual en el viewpager
  • Actividad con pestañas de Android Abajo de la pantalla
  • Fragmento anterior visible bajo el nuevo fragmento
  • Dos fragmentos en una página de viewpager
  • Actualizar imágenes en FragmentStatePagerAdapter al reanudar la actividad
  • Efecto de desplazamiento con múltiples visualizadores
  • Android: Fragment y ViewPager comienza siempre en la primera posición
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.