2012-02-26 10 views
18

Zrobiłem prosty Android Activity z paskami akcji, aby przełączać się między 2 fragmentami. Wszystko jest w porządku, dopóki nie obrócę urządzenia. W rzeczywistości, kiedy się obracam, mam 2 fragmenty jeden nad drugim: poprzedni aktywny i pierwszy. Dlaczego? Jeśli rotacja niszczy i odtwarza moją aktywność, dlaczego otrzymuję 2 fragmenty?Podwójny fragment obracającego się systemu Android z ActionBarem

kod

Próbka:

aktywny

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

Odpowiedz

20

mam rozwiązany za pomocą onSaveInstanceState i onRestoreInstanceState w Działaniu, aby utrzymać wybraną kartę i modyfikację onTabSelected jak następuje.

Ostatnia modyfikacja pozwala na to, że Android nie odbuduje fragmentu, który nie będzie niszczony. Nie rozumiem jednak, dlaczego Działanie zostało zniszczone przez zdarzenie rotacyjne, podczas gdy bieżący Fragment nie. (Czy masz jakiś pomysł na ten temat?)

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

To działało idealnie. Wszelka wskazówka, czy jest to "właściwy" sposób robienia rzeczy? –

+3

Aż znalazłem to, co doprowadziło mnie do szału ... Dziękuję bardzo za tę odpowiedź! (Nadal nie mogę uwierzyć, że Google tego nie naprawi w swoim przykładzie) – Patrick

+0

Interesujące. Korzystam tylko z portretu "android.support.v4.view.ViewPager" i wyświetlałem fragmenty w tle. Obróć dwa razy i dostaję dwa fragmenty w pejzażu. Jeden nazywający coś jak * android: switcher: 2131099773: 1 * - doprowadza mnie do szału. Muszę znaleźć staw skokowy, aby pozbyć się zombie. – Martin

11

Ponieważ używam android.support.v4.view.ViewPager unieważniając onTabSelected nie pomoże. Ale mimo to wskazówka wskazała mi właściwy kierunek.

Zapisuje wszystkie fragmenty wz android.support.v4.app.FragmentActivity. Ignorowanie setRetainInstance - W zależności od projektu może to prowadzić do duplikowania fragmentów.

Najprostszym rozwiązaniem jest usunięcie zapisanego fragmentu w orCreate działalności:

@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 Nigdy byśmy tego nie znaleźli. – Patrick

+1

To działało dobrze, z wyjątkiem tego, że gdy Fragment2 przechodzi w Krajobraz, wyświetlany jest fragment1. Nie jestem pewien, czy jestem jedynym, który ma ten problem. Jakieś rozwiązanie? – Art

+0

Współpracuje również z Twoim rozwiązaniem w orientacji pionowej i poziomej. nie zachodzą na siebie po przełączeniu między kartami. –

3

rozwiązanie nie rzeczywiście wymaga dużo pracy, następujące kroki zapewnienia, że ​​podczas obracania ekranu wybór zakładek zostanie zachowany. Wpadłem na nakładające się Fragmenty, ponieważ po obróceniu ekranu wybrano pierwszą kartę, a nie drugą wybraną przed obróceniem ekranu, a tym samym pierwsza zakładka pokrywała się z zawartością drugiej zakładki.

ten sposób swoją działalność powinna wyglądać (używam ActionBarSherlock ale dostosowanie powinno być bardzo łatwe):

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

I to moja ActionBar.TabListener wygląda (jego prywatnej klasy w powyższym Aktywność):

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 
    } 

Powyższa implementacja umożliwia również wymianę Fragmentów w zakładce, w oparciu o ich znaczniki. W tym celu podczas przełączania fragmentów na tej samej karcie używam tej samej nazwy znacznika, która została użyta w początkowej strukturze, która została dodana do karty.

0

Dzięki Martin i asclepix dla swoich rozwiązań.Mam 3 karty i pierwsza zakładka zawiera 2 fragmenty, na przykład:

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

Korzystanie onRestoreInstanceState, onSaveInstanceState i savedInstanceState.remove("android:support:fragments"); metod i oświadczenie pracuje prawie w porządku z wyjątkiem jeśli aktywna karta nie jest pierwszy i obróć i kliknij pierwszy, pojawi się wyraźny ekran i dopiero po drugim kliknięciu na pierwszej karcie pojawi się ekran prawego fragmentu. Po debugowanie kodu Poznałem, że pierwszy addTab zawsze wywołuje zdarzenie w zakładce onTabSelected słuchacza, z fragmentem add metody, a następnie, gdy setSelectedNavigationItem jest wywoływana z onRestoreInstanceStatedetach jest wykonywany na pierwszej karcie i add za drugim. To niepotrzebne wywołanie add jest naprawione w moim rozwiązaniu.

Moja aktywność

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

I zmodyfikowany zakładka słuchacz

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. 
    } 
} 
Powiązane problemy