2012-02-26 20 views
18

He hecho una actividad simple de Android con una barra de acción para alternar entre 2 fragmentos. Todo está bien hasta que gire el dispositivo. En realidad, cuando giro tengo 2 fragmentos uno sobre el otro: el anterior activo y el primero. ¿Por qué? Si la rotación destruye y recrea mi actividad, ¿por qué obtengo 2 fragmentos?Doble fragmento que rota Android con ActionBar

código

muestra:

Actividad

package rb.rfrag.namespace; 

import android.app.ActionBar; 
import android.app.ActionBar.Tab; 
import android.app.Activity; 
import android.os.Bundle; 

    public class RFragActivity extends Activity { 
    /** Called when the activity is first created. */ 
    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     // Notice that setContentView() is not used, because we use the root 
     // android.R.id.content as the container for each fragment 

    // setup action bar for tabs 
     final ActionBar actionBar = getActionBar(); 
     actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); 
     //actionBar.setDisplayShowTitleEnabled(false); 

     Tab tab; 
     tab = actionBar.newTab() 
       .setText(R.string.VarsTab) 
       .setTabListener(new TabListener<VarValues>(
         this, "VarValues", VarValues.class)); 
     actionBar.addTab(tab); 

     tab = actionBar.newTab() 
       .setText(R.string.SecTab) 
       .setTabListener(new TabListener<SecFrag>(
         this, "SecFrag", SecFrag.class)); 
     actionBar.addTab(tab); 
    } 
} 

TabListener

package rb.rfrag.namespace; 

import android.app.ActionBar; 
import android.app.Activity; 
import android.app.Fragment; 
import android.app.FragmentManager; 
import android.app.FragmentTransaction; 
import android.app.ActionBar.Tab; 

public class TabListener<T extends Fragment> implements ActionBar.TabListener { 
    private Fragment mFragment; 
    private final Activity mActivity; 
    private final String mTag; 
    private final Class<T> mClass; 

    /** Constructor used each time a new tab is created. 
     * @param activity The host Activity, used to instantiate the fragment 
     * @param tag The identifier tag for the fragment 
     * @param clz The fragment's Class, used to instantiate the fragment 
     */ 
    public TabListener(Activity activity, String tag, Class<T> clz) { 
     mActivity = activity; 
     mTag = tag; 
     mClass = clz; 
    } 

    /* The following are each of the ActionBar.TabListener callbacks */ 

    public void onTabSelected(Tab tab, FragmentTransaction ft) {  
     // Check if the fragment is already initialized 
     if (mFragment == null) { 
      // If not, instantiate and add it to the activity 
      mFragment = Fragment.instantiate(mActivity, mClass.getName()); 
      ft.add(android.R.id.content, mFragment, mTag); 
     } else { 
      // If it exists, simply attach it in order to show it 
      ft.attach(mFragment); 
     } 
    } 

    public void onTabUnselected(Tab tab, FragmentTransaction ft) { 
     if (mFragment != null) { 
      // Detach the fragment, because another one is being attached 
      ft.detach(mFragment); 
     } 
    } 
} 

Respuesta

20

he resuelto utilizando onSaveInstanceState y onRestoreInstanceState en la Actividad de mantener la ficha seleccionada y modificar onTabSelected de la siguiente manera.

La última modificación evita que Android reconstruya el Fragmento que no desea. Sin embargo, no entiendo por qué la Actividad es destruida por el evento de rotación mientras que el Fragmento actual no. (¿Tiene usted alguna idea sobre esto?)

public void onTabSelected(Tab tab, FragmentTransaction ft) { 
     // previous Fragment management 
     Fragment prevFragment; 
     FragmentManager fm = mActivity.getFragmentManager(); 
     prevFragment = fm.findFragmentByTag(mTag); 
     if (prevFragment != null) { 
      mFragment = prevFragment; 
     } // \previous Fragment management 

     // Check if the fragment is already initialized 
     if (mFragment == null) { 
      // If not, instantiate and add it to the activity 
      mFragment = Fragment.instantiate(mActivity, mClass.getName()); 
      ft.add(android.R.id.content, mFragment, mTag); 
     } else { 
      // If it exists, simply attach it in order to show it 
      ft.attach(mFragment); 
     } 
    } 
+1

Esto funcionó a la perfección. ¿Alguna pista sobre si esta es la forma "correcta" de hacer las cosas? –

+3

Hasta que encontré esto, eso me volvía loco ... ¡Muchas gracias por esta respuesta! (Todavía no puedo creer que Google no lo solucione en su ejemplo) – Patrick

+0

Interesante. Solo uso un 'android.support.v4.view.ViewPager' en vertical y visualizo ambos fragmentos cuando el paisaje. Gira dos veces y obtengo dos fragmentos en el paisaje. Uno llamado algo así como * android: switcher: 2131099773: 1 * - me vuelve loco. Tengo que encontrar un corvejón para deshacerme del zombie. – Martin

11

Desde que uso un android.support.v4.view.ViewPager anulando onTabSelected no ayudaría. Pero todavía me insinúa que me indicó la dirección correcta.

android.support.v4.app.FragmentManager guarda todos los fragmentos en onSaveInstanceState del android.support.v4.app.FragmentActivity. Ignorando setRetainInstance - Dependiendo de su diseño, esto podría generar la duplicación de fragmentos.

La solución más sencilla consiste en eliminar el fragmento guardado en la orCreate de la actividad:

@Override 
    public void onCreate (final android.os.Bundle savedInstanceState) 
    { 
     if (savedInstanceState != null) 
     { 
     savedInstanceState.remove ("android:support:fragments"); 
     } // if 

     super.onCreate (savedInstanceState); 
… 
     return; 
    } // onCreate 
+0

+1000 Nunca habría encontrado esto. – Patrick

+1

Esto funcionó bien, excepto que cuando Fragment2 entra en Landscape, se muestra fragment1. No estoy seguro si soy el único que tiene este problema. ¿Alguna solución? – Art

+0

Funciona con su solución también en retrato y paisaje. ya no se superpone después de cambiar entre pestañas. –

3

La solución en realidad no requiere una gran cantidad de trabajo, los siguientes pasos aseguran, que al girar la pantalla, la selección de pestañas se mantiene. Me topé con Fragmentos solapados, porque al rotar la pantalla se seleccionó mi primera pestaña, no la segunda que se seleccionó antes de girar la pantalla y, por lo tanto, la primera pestaña superpuso el contenido de la segunda pestaña.

Esta es la forma en que su actividad debe ser (estoy usando ActionBarSherlock pero ajustándolo debería ser muy fácil):

public class TabHostActivity extends SherlockFragmentActivity { 

    private static final String SELETED_TAB_INDEX = "tabIndex"; 


    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 

    // Setup the action bar 
    ActionBar actionBar = getSupportActionBar(); 
    actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); 

    // Create the Tabs you need and add them to the actionBar... 

    if (savedInstanceState != null) { 
     // Select the tab that was selected before orientation change 
     int index = savedInstanceState.getInt(SELETED_TAB_INDEX); 
     actionBar.setSelectedNavigationItem(index); 
    } 
    } 

    @Override 
    protected void onSaveInstanceState(Bundle outState) { 
    super.onSaveInstanceState(outState); 
    // Save the index of the currently selected tab 
    outState.putInt(SELETED_TAB_INDEX, getSupportActionBar().getSelectedTab().getPosition()); 
    } 
} 

Y esto es lo que mi ActionBar.TabListener parece (es una clase privada en la anterior Actividad):

private class MyTabsListener<T extends Fragment> implements ActionBar.TabListener { 
    private Fragment fragment; 
    private final SherlockFragmentActivity host; 
    private final Class<Fragment> type; 
    private String tag; 

    public MyTabsListener(SherlockFragmentActivity parent, String tag, Class type) { 
     this.host = parent; 
     this.tag = tag; 
     this.type = type; 
    } 

    @Override 
    public void onTabSelected(Tab tab, FragmentTransaction transaction) { 
     /* 
     * The fragment which has been added to this listener may have been 
     * replaced (can be the case for lists when drilling down), but if the 
     * tag has been retained, we should find the actual fragment that was 
     * showing in this tab before the user switched to another. 
     */ 
     Fragment currentlyShowing = host.getSupportFragmentManager().findFragmentByTag(tag); 

     // Check if the fragment is already initialised 
     if (currentlyShowing == null) { 
      // If not, instantiate and add it to the activity 
      fragment = SherlockFragment.instantiate(host, type.getName()); 
      transaction.add(android.R.id.content, fragment, tag); 
     } else { 
      // If it exists, simply attach it in order to show it 
      transaction.attach(currentlyShowing); 
     } 
    } 

    public void onTabUnselected(Tab tab, FragmentTransaction fragmentTransaction) { 
     /* 
     * The fragment which has been added to this listener may have been 
     * replaced (can be the case for lists when drilling down), but if the 
     * tag has been retained, we should find the actual fragment that's 
     * currently active. 
     */ 
     Fragment currentlyShowing = host.getSupportFragmentManager().findFragmentByTag(tag); 
     if (currentlyShowing != null) { 
      // Detach the fragment, another tab has been selected 
      fragmentTransaction.detach(currentlyShowing); 
     } else if (this.fragment != null) { 
      fragmentTransaction.detach(fragment); 
     } 
    } 

    public void onTabReselected(Tab tab, FragmentTransaction fragmentTransaction) { 
     // This tab is already selected 
    } 

La implementación anterior también permite el reemplazo de Fragmentos dentro de una pestaña, en función de sus etiquetas. Para este propósito, al cambiar fragmentos dentro de la misma pestaña, uso el mismo nombre de etiqueta, que se usó para el marco inicial que se ha agregado a la pestaña.

0

Gracias Martin y asclepix por sus soluciones.Tengo 3 pestañas y la primera pestaña contiene 2 fragmentos, así:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    xmlns:tools="http://schemas.android.com/tools" 
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent" 
    tools:context=".MainActivity" > 

    <FrameLayout 
     android:id="@+id/frActiveTask" 
     android:layout_width="fill_parent" 
     android:layout_height="50dp" 
     android:layout_alignParentBottom="true" 
     /> 

    <FrameLayout 
     android:id="@+id/frTaskList" 
     android:layout_width="fill_parent" 
     android:layout_height="match_parent" 
     android:layout_alignParentTop="true" 
     android:layout_above="@id/frActiveTask" 
     /> 

</RelativeLayout> 

Uso onRestoreInstanceState, onSaveInstanceState y savedInstanceState.remove("android:support:fragments"); métodos y la declaración de trabajo casi bien, excepto si su ficha activa no es la primera y girar y haga clic en el primero, aparece una pantalla clara y solo durante el segundo clic en la primera pestaña aparece la visualización del fragmento correcto. Después de la depuración del código reconocí que el primer addTab siempre llama a un evento onTabSelected en el oyente pestaña, con un método add fragmento y luego, cuando el setSelectedNavigationItem se llama desde un onRestoreInstanceStatedetach se ejecuta en la primera pestaña y un add para otro. Esta innecesaria llamada add está solucionada en mi solución.

Mi actividad

protected void onCreate(Bundle savedInstanceState) { 
    boolean firstTabIsNotAdded = false; 
    if (savedInstanceState != null) { 
     savedInstanceState.remove("android:support:fragments"); 
     firstTabIsNotAdded = savedInstanceState.getInt(SELETED_TAB_INDEX) != 0; 
    } 
    super.onCreate(savedInstanceState); 

    setContentView(R.layout.activity_main); 

// codes before adding tabs 

    actionBar = getSupportActionBar(); 
    actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); 


    tabStartAndStop = actionBar.newTab().setText(getString(R.string.tab_title_start_and_stop)) 
      .setTabListener(
        new FragmentTabListener<StartStopFragment>(this, 
          getString(R.string.tab_title_start_and_stop_id), 
          StartStopFragment.class, 
          firstTabIsNotAdded)); 
    tabHistory = actionBar.newTab().setText(getString(R.string.tab_title_history)) 
      .setTabListener(
        new FragmentTabListener<HistoryFragment>(this, 
          getString(R.string.tab_title_history_id), 
          HistoryFragment.class, 
          false)); 
    tabRiporting = actionBar.newTab().setText(getString(R.string.tab_title_reporting)) 
      .setTabListener(
        new FragmentTabListener<ReportingFragment>(this, 
          getString(R.string.tab_title_reporting_id), 
          ReportingFragment.class, 
          false)); 

    actionBar.addTab(tabStartAndStop); 

     actionBar.addTab(tabHistory); 
     actionBar.addTab(tabRiporting); 

    } 

    @Override 
    protected void onRestoreInstanceState(Bundle savedInstanceState) { 
     if (savedInstanceState != null) { 
      int index = savedInstanceState.getInt(SELETED_TAB_INDEX); 
      actionBar.setSelectedNavigationItem(index); 
     } 
     super.onRestoreInstanceState(savedInstanceState); 
    } 

    @Override 
    protected void onSaveInstanceState(Bundle outState) { 
     super.onSaveInstanceState(outState); 
     // Save the index of the currently selected tab 
     outState.putInt(SELETED_TAB_INDEX, getSupportActionBar().getSelectedTab().getPosition()); 
    } 

Y el oyente pestaña modificado

public class FragmentTabListener<T extends SherlockFragment> implements com.actionbarsherlock.app.ActionBar.TabListener { 
    private Fragment mFragment; 
    private final Activity mFragmentActivity; 
    private final String mTag; 
    private final Class<T> mClass; 
    private boolean doNotAdd; 

    /** Constructor used each time a new tab is created. 
     * @param activity The host Activity, used to instantiate the fragment 
     * @param tag The identifier tag for the fragment 
     * @param clz The fragment's Class, used to instantiate the fragment 
     */ 
    public FragmentTabListener(Activity activity, String tag, Class<T> clz, boolean doNotAdd) { 
     mFragmentActivity = activity; 
     mTag = tag; 
     mClass = clz; 
     this.doNotAdd = doNotAdd; 
    } 

    /* The following are each of the ActionBar.TabListener callbacks */ 
    public void onTabSelected(Tab tab, FragmentTransaction ft) { 

     // Check if the fragment is already initialized 
     if (mFragment == null) { 
      // If not, instantiate and add it to the activity 
      if(doNotAdd){ 
       doNotAdd = false; 
      }else{ 
       mFragment = Fragment.instantiate(mFragmentActivity, mClass.getName()); 
       ft.add(android.R.id.content, mFragment, mTag); 
      } 
     } else { 
      // If it exists, simply attach it in order to show it 
      ft.attach(mFragment); 
     } 
    } 

    public void onTabUnselected(Tab tab, FragmentTransaction ft) { 
     if (mFragment != null) { 
      // Detach the fragment, because another one is being attached 
      ft.detach(mFragment); 
     } 
    } 

    public void onTabReselected(Tab tab, FragmentTransaction ft) { 
     // User selected the already selected tab. Usually do nothing. 
    } 
} 
Cuestiones relacionadas