2012-02-26 5 views
18

Ho creato una semplice attività Android con un ActionBar per passare da 2 frammenti. Va tutto bene finché non ruoto il dispositivo. In effetti, quando ruoto ho 2 frammenti uno sull'altro: il precedente attivo e il primo. Perché? Se la rotazione distrugge e ricrea la mia attività, perché ottengo 2 frammenti?Doppio frammento rotante Android con ActionBar

codice

Esempio:

Attività

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); 
     } 
    } 
} 

risposta

20

ho risolto utilizzando onSaveInstanceState e onRestoreInstanceState in attività per mantenere la scheda selezionata e la modifica onTabSelected come segue.

L'ultima modifica evita che Android ricostruisca il frammento che non gira. Tuttavia non capisco perché l'attività sia distrutta dall'evento di rotazione mentre il frammento attuale n. (Hai qualche idea su questo?)

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

Questo ha funzionato perfettamente. Qualche indizio se questo è il modo "corretto" di fare le cose? –

+3

Fino a quando ho trovato questo, mi ha fatto impazzire ... Grazie mille per questa risposta! (Ancora non riesco a credere che Google non avrebbe risolto quello nel loro esempio) – Patrick

+0

Interessante. Solo io uso un 'android.support.v4.view.ViewPager' in verticale e visualizzo entrambi i frammenti quando sono orizzontali. Ruota due volte e ottengo due frammenti in orizzontale. Uno chiamato qualcosa come * Android: switcher: 2131099773: 1 * - mi fa impazzire. Devo trovare un garbuglio per liberarmi dello zombi. – Martin

11

Da quando uso un android.support.v4.view.ViewPager sovrascrivendo onTabSelected non sarebbe di aiuto. Ma comunque mi hai indicato nella giusta direzione.

Il android.support.v4.app.FragmentManager salva tutti i frammenti nello onSaveInstanceState dello android.support.v4.app.FragmentActivity. Ignorare setRetainInstance - A seconda del progetto, questo potrebbe portare a frammenti duplicati.

La soluzione più semplice è quella di eliminare il frammento salvato nel orCreate dell'attività:

@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 Non avrei mai trovato questo. – Patrick

+1

Questo ha funzionato bene tranne che quando Fragment2 entra in Landscape, viene visualizzato framment1. Non sono sicuro se io sono l'unico che ha questo problema. Qualche soluzione? – Art

+0

Funziona con la tua soluzione anche in verticale e orizzontale. non si sovrappone più dopo il passaggio tra le schede. –

3

La soluzione in realtà non richiede un sacco di lavoro, i seguenti passaggi assicurano che, quando la rotazione dello schermo, la selezione della scheda viene mantenuta. Mi sono imbattuto in frammenti sovrapposti, perché dopo la rotazione dello schermo è stata selezionata la prima scheda, non la seconda selezionata prima di ruotare lo schermo e quindi la prima scheda si sovrapponeva al contenuto della seconda scheda.

Questo è come il vostro attività dovrebbe apparire (sto usando ActionBarSherlock ma adeguandolo dovrebbe essere molto facile):

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()); 
    } 
} 

e questo è ciò che il mio ActionBar.TabListener sembra (è una classe privata in quanto sopra Attività):

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 
    } 

Quanto sopra implementazione permette inoltre la sostituzione di frammenti all'interno di una scheda, in base alle loro etichette. A tale scopo, quando si commutano i frammenti all'interno della stessa scheda, utilizzo lo stesso nome di tag, che è stato utilizzato per il framework iniziale che è stato aggiunto alla scheda.

0

Grazie a Martin e asclepix per le loro soluzioni.Ho 3 schede e prima scheda contiene 2 frammenti, come questo:

<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 e savedInstanceState.remove("android:support:fragments"); metodi e la dichiarazione di lavoro quasi bene, tranne se la vostra scheda attiva non è il primo e ruotare e fare clic sul primo, viene visualizzato un display chiaro e solo per il secondo clic sulla prima scheda viene visualizzato il display del frammento corretto. Dopo debug di codice ho riconosciuto che la prima addTab chiama sempre un evento onTabSelected nella scheda ascoltatore, con un metodo add frammento e poi quando il setSelectedNavigationItem viene chiamato da un onRestoreInstanceStatedetach viene eseguita nella prima scheda e un add per altro. Questa chiamata non necessaria add è fissa nella mia soluzione.

mia attività

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()); 
    } 

E l'ascoltatore scheda modificata

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. 
    } 
}