Handle Fragment duplication on Screen Rotate (con código de ejemplo)

Hay algunas respuestas similares, pero no a esta situación.


Mi situación es simple.

Tengo una actividad con dos diseños diferentes, uno en el retrato, otro en el paisaje.

En Retrato , utilizo <FrameLayout> y agrego Fragment en él dinámicamente .

En Paisaje , utilizo <fragment> para que el Fragment sea estático . (De hecho esto no importa)

Primero empieza en Retrato , luego agregué el Fragment simplemente:

 ListFrag listFrag = new ListFrag(); getSupportFragmentManager().beginTransaction() .replace(R.id.FragmentContainer, listFrag).commit(); 

Donde ListFrag extends ListFragment .

Entonces hago una rotación de la pantalla. Encontré que el listFrag está recreando en el modo de paisaje. (En el que me di cuenta de que el método onCreate() se llama de nuevo con un paquete no nulo)

Intenté usar setRetainInstance(false) como @NPike dijo en este post . Pero el getRetainInstance() ya es false por defecto. No hace lo que esperaba, como dijeron los docs . ¿Podría alguien explicar?


El fragmento que estoy tratando, es un ListFragment , que hace setListAdapter() en onCreate() . Así que if (container == null) return null; No se puede utilizar aquí. (O no sé cómo aplicar).

Tengo algunas pistas de este post . ¿Debo usar if (bundle != null) setListAdapter(null); else setListAdapter(new ...); if (bundle != null) setListAdapter(null); else setListAdapter(new ...); En mi ListFragment ? ¿Pero hay una manera más agradable de quitar / suprimir realmente el fragmento cuando es destruido / separado, más bien que lo hace en su tiempo de la creación? (De forma que el método if (container == null) return null;


Editar:

La única manera ordenada que encontré es hacer getSupportFragmentManager().beginTransaction().remove(getSupportFragmentManager().findFragmentById(R.id.FragmentContainer)).commit(); En onSaveInstanceState() . Pero planteará otros problemas.

  1. Cuando la pantalla está parcialmente oscurecida, como los cuadros de diálogo de WhatsApp o TXT, el fragmento desaparecerá también. (Esto es relativamente menor, sólo problema visual)

  2. Cuando la pantalla gira, la Actividad se destruye completamente y se vuelve a crear. Así que puedo volver a agregar el fragmento en onCreate(Bundle) o onRestoreInstanceState(Bundle) . Pero en el caso de (1), así como las Actividades de conmutación, ni onCreate(Bundle) ni onRestoreInstanceState(Bundle) serán llamados cuando el usuario regrese a mi Actividad. No tengo ningún lugar para recrear la actividad (y recuperar los datos de Bundle).

Lo siento, no lo dije claramente que, ya tengo una toma de decisiones, que el getSupportFragmentManager()...replace(...).commit(); La línea sólo se ejecuta en modo vertical.


Ejemplo de código

He extraído el código simple, para mejor ilustración de la situación 🙂

MainActivity.java

 package com.example.fragremovetrial; import android.os.Bundle; import android.support.v4.app.FragmentActivity; public class MainActivity extends FragmentActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if (findViewById(R.id.FragmentContainer) != null) { System.out.println("-- Portrait --"); // Portrait ListFrag listFrag = new ListFrag(); getSupportFragmentManager().beginTransaction() .replace(R.id.FragmentContainer, listFrag).commit(); } else { System.out.println("-- Landscape --"); // Landscape } } @Override protected void onResume() { super.onResume(); if (findViewById(R.id.FragmentContainer) != null) { System.out.println("getRetainInstance = " + getSupportFragmentManager().findFragmentById(R.id.FragmentContainer).getRetainInstance()); } } } 

Layout / activity_main.xml

 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/FragmentContainer" android:layout_width="fill_parent" android:layout_height="fill_parent" /> 

Layout-land / activity_main.xml (no importa)

 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > </LinearLayout> 

ListFrag.java

 package com.example.fragremovetrial; import android.os.Bundle; import android.support.v4.app.ListFragment; import android.widget.ArrayAdapter; public class ListFrag extends ListFragment { private String[] MenuItems = { "Content A", "Contnet B" }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); System.out.println("ListFrag.onCreate(): " + (savedInstanceState == null ? null : savedInstanceState)); setListAdapter(new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, MenuItems)); } } 

Aviso que tengo el siguiente

Mensajes de depuración

– Retrato –
ListFrag.onCreate (): null
GetRetainInstance = false

(Girar Puerto -> Tierra)

ListFrag.onCreate (): Bundle [{android: view_state=android.util.SparseArray@4052dd28}]
– Paisaje –
La vista previamente enfocada informó el ID 16908298 durante la grabación, pero no se puede encontrar durante la restauración.

(Rotar Tierra -> Puerto)

ListFrag.onCreate (): Bundle [{android: view_state=android.util.SparseArray@405166c8}]
– Retrato –
ListFrag.onCreate (): null
GetRetainInstance = false

(Girar Puerto -> Tierra)

ListFrag.onCreate (): Paquete [{android: view_state=android.util.SparseArray@4050fb40}]
– Paisaje –
La vista previamente enfocada informó el ID 16908298 durante la grabación, pero no se puede encontrar durante la restauración.

(Rotar Tierra -> Puerto)

ListFrag.onCreate (): Paquete [{android: view_state=android.util.SparseArray@40528c60}]
– Retrato –
ListFrag.onCreate (): null
GetRetainInstance = false

Donde el número de Fragmentos creados no aumentará infinitamente a medida que la pantalla siga girando, cosa que no puedo explicar. (por favor ayuda)

Finalmente llegué con una solución para evitar que Fragmento no deseado vuelva a crear en la actividad se vuelve a crear. Es como lo que mencioné en la pregunta:

La única manera ordenada que encontré es hacer getSupportFragmentManager().beginTransaction().remove(getSupportFragmentManager().findFragmentById(R.id.FragmentContainer)).commit(); En onSaveInstanceState() . Pero planteará otros problemas …

… con algunas mejoras.

En onSaveInstanceState() :

 @Override protected void onSaveInstanceState(Bundle outState) { if (isPortrait2Landscape()) { remove_fragments(); } super.onSaveInstanceState(outState); } private boolean isPortrait2Landscape() { return isDevicePortrait() && (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE); } 

Y el isDevicePortrait() sería como:

 private boolean isDevicePortrait() { return (findViewById(R.id.A_View_Only_In_Portrait) != null); } 

* Tenga en cuenta que no podemos usar la getResources().getConfiguration().orientation para determinar si el dispositivo está literalmente en vertical. Es porque el objeto Resources se cambia DERECHO DESPUÉS de que la pantalla gira – ¡ AÚN ANTES de onSaveInstanceState() !

Si no queremos usar findViewById() para probar la orientación (por cualquier razón, y no es tan agradable después de todo), mantenga una variable global private int current_orientation; Y lo inicializamos por current_orientation = getResources().getConfiguration().orientation; En onCreate() . Esto parece más limpio. Pero debemos ser conscientes de no cambiarlo en ninguna parte durante el ciclo de vida de la actividad.

* Asegúrese de que remove_fragments() antes de super.onSaveInstanceState() .

(Porque en mi caso, elimino los Fragmentos del Layout y de la Actividad.Si es después de super.onSaveInstanceState() , el Layout ya se guardará en el Bundle. Entonces los Fragmentos también serán recreados después del La actividad se vuelve a crear. ###)

### He probado este fenómeno. Pero la razón de ¿Qué para determinar un Fragmento de restauración sobre actividad volver a crear? Es sólo por mi conjetura. Si tiene alguna idea al respecto, responda a mi otra pregunta .

La forma correcta de manejar esto es poner lo siguiente en su onCreate ()

 if (savedInstanceState == null) { // Do fragment transaction. } 

En su método onCreate() sólo debe crear su ListFrag si está en modo retrato. Puede hacerlo comprobando si la vista FrameLayout que sólo tiene en el diseño vertical no es null .

 if (findViewById(R.id.yourFrameLayout) != null) { // you are in portrait mode ListFrag listFrag = new ListFrag(); getSupportFragmentManager().beginTransaction() .replace(R.id.FragmentContainer, listFrag).commit(); } else { // you are in landscape mode // get your Fragment from the xml } 

Al final, si cambia de la presentación vertical a la horizontal, no desea crear otro ListFrag, pero utilice el fragmento que ha especificado en su xml.

@midnite, en primer lugar gracias por su pregunta, tuve la misma pregunta, en la que trabajé durante días. Ahora he herido una solución difícil para este problema – y quiero compartir esto con la comunidad. Pero si alguien tiene mejor solución, por favor escriba en comentarios.

Así pues, tengo la misma situación – diversas disposiciones para la orientación de dos dispositivos. En el retrato no hay necesidad de DetailsFragment.

Simplemente en OnCreate tratando de encontrar fragmentos ya creados:

  fragment = (ToDoListFragment) getFragmentManager().findFragmentByTag(LISTFRAGMENT); frameDetailsFragment = (FrameLayout) findViewById(R.id.detailsFragment); detailsFragment = (DetailsFragment) getFragmentManager().findFragmentByTag(DETAILS_FRAGMENT); settingsFragment = (SettingsFragment) getFragmentManager().findFragmentByTag(SETTINGS_FRAGMENT); 

Justo después de eso estoy agregando mi fragmento principal –

  if (fragment == null){ fragment = new ToDoListFragment(); getFragmentManager().beginTransaction() .add(R.id.container, fragment, LISTFRAGMENT) .commit(); } 

Y claro de nuevo el stask de mis fragmentos:

  if (getFragmentManager().getBackStackEntryCount() > 0 ) { getFragmentManager().popBackStackImmediate(); } 

Y lo más interesante es aquí

  destroyTmpFragments(); 

Aquí está el método en sí:

  private void destroyTmpFragments(){ if (detailsFragment != null && !detailsFragment.isVisible()) { Log.d("ANT", "detailsFragment != null, Destroying"); getFragmentManager().beginTransaction() .remove(detailsFragment) .commit(); detailsFragment = null; } if (settingsFragment != null && !settingsFragment.isVisible()) { Log.d("ANT", "settingsFragment != null, Destroying"); getFragmentManager().beginTransaction() .remove(settingsFragment) .commit(); settingsFragment = null; } } 

Como se puede ver, limpio manualmente todos los fragmentos, que FragmentManager cuidadosamente guardado para mí (gran gracias a él). En el registro tengo próximas llamadas del ciclo de vida:

  04-24 23:03:27.164 3204 3204 D ANT MainActivity onCreate() 04-24 23:03:27.184 3204 3204 I ANT DetailsFragment :: onCreateView 04-24 23:03:27.204 3204 3204 I ANT DetailsFragment :: onActivityCreated 04-24 23:03:27.204 3204 3204 I ANT DetailsFragment :: onDestroy 04-24 23:03:27.208 3204 3204 I ANT DetailsFragment :: onDetach 

Así que en onActivityCreated haga que sus vistas comprueben si hay nulo – en caso de que el fragmento no esté visible. Después, en el código de la actividad podemos crear una nueva instancia de Fragmento, con el diseño adecuado (el marcador de posición de fargmnet) y el fragmento será capaz de encontrar sus vistas, por ejemplo , Y no producirá NullPointerException

Pero, los fragmentos que están en la pila trasera se eliminan bastante rápido, sin llamar a onActivityCreated (con la ayuda th del código anterior:

  if (getFragmentManager().getBackStackEntryCount() > 0 ) { getFragmentManager().popBackStackImmediate(); } 

Por último, al final agrego fragmento si estoy en orientación de tierra –

  if (frameDetailsFragment != null){ Log.i("ANT", "frameDetailsFragment != null"); if (EntryPool.getPool().getEntries().size() > 0) { if (detailsFragment == null) { detailsFragment = DetailsFragment.newInstance(EntryPool.getPool().getEntries().get(0), 0); } getFragmentManager().beginTransaction() .replace(R.id.detailsFragment, detailsFragment, DETAILS_FRAGMENT) .commit(); } } 
  • Panel corredero deslizante - panel deslizante de línea fina bajo google mapfragment
  • problemas con Android FrameLayout cuando override onDraw
  • Cómo obtener clics en dos botón solapado en la misma disposición de marco en android
  • El botón siempre aparece en la parte superior de FrameLayout
  • ¿Qué hace FrameLayout?
  • Imagen de fondo de pantalla completa en una actividad
  • Uso de FrameLayout como fondo de pantalla en vivo
  • La llamada requiere el nivel 23 de API: error getForeground (), pero es un método FrameLayout ya que API 1
  • Android: cómo escalar una imagen para llenar el fondo de un LinearLayout sin preservar la relación de aspecto?
  • ClassCastException LinearLayout LayoutParams
  • Advertencia: Este <FrameLayout> se puede reemplazar con una etiqueta <merge>
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.