Rozwijam aplikację przy użyciu systemu Android 4.0 ICS i fragmentów.
Rozważmy ten zmodyfikowany przykład z przykładowej aplikacji demonstracyjnej interfejsu API ICS 4.0.3 (poziom API 15):
public class FragmentTabs extends Activity {
private static final String TAG = FragmentTabs.class.getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final ActionBar bar = getActionBar();
bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
bar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE);
bar.addTab(bar.newTab()
.setText("Simple")
.setTabListener(new TabListener<SimpleFragment>(
this, "mysimple", SimpleFragment.class)));
if (savedInstanceState != null) {
bar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0));
Log.d(TAG, "FragmentTabs.onCreate tab: " + savedInstanceState.getInt("tab"));
Log.d(TAG, "FragmentTabs.onCreate number: " + savedInstanceState.getInt("number"));
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("tab", getActionBar().getSelectedNavigationIndex());
}
public static class TabListener<T extends Fragment> implements ActionBar.TabListener {
private final Activity mActivity;
private final String mTag;
private final Class<T> mClass;
private final Bundle mArgs;
private Fragment mFragment;
public TabListener(Activity activity, String tag, Class<T> clz) {
this(activity, tag, clz, null);
}
public TabListener(Activity activity, String tag, Class<T> clz, Bundle args) {
mActivity = activity;
mTag = tag;
mClass = clz;
mArgs = args;
// Check to see if we already have a fragment for this tab, probably
// from a previously saved state. If so, deactivate it, because our
// initial state is that a tab isn't shown.
mFragment = mActivity.getFragmentManager().findFragmentByTag(mTag);
if (mFragment != null && !mFragment.isDetached()) {
Log.d(TAG, "constructor: detaching fragment " + mTag);
FragmentTransaction ft = mActivity.getFragmentManager().beginTransaction();
ft.detach(mFragment);
ft.commit();
}
}
public void onTabSelected(Tab tab, FragmentTransaction ft) {
if (mFragment == null) {
mFragment = Fragment.instantiate(mActivity, mClass.getName(), mArgs);
Log.d(TAG, "onTabSelected adding fragment " + mTag);
ft.add(android.R.id.content, mFragment, mTag);
} else {
Log.d(TAG, "onTabSelected attaching fragment " + mTag);
ft.attach(mFragment);
}
}
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
if (mFragment != null) {
Log.d(TAG, "onTabUnselected detaching fragment " + mTag);
ft.detach(mFragment);
}
}
public void onTabReselected(Tab tab, FragmentTransaction ft) {
Toast.makeText(mActivity, "Reselected!", Toast.LENGTH_SHORT).show();
}
}
public static class SimpleFragment extends Fragment {
TextView textView;
int mNum;
/**
* When creating, retrieve this instance's number from its arguments.
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(FragmentTabs.TAG, "onCreate " + (savedInstanceState != null ? ("state " + savedInstanceState.getInt("number")) : "no state"));
if(savedInstanceState != null) {
mNum = savedInstanceState.getInt("number");
} else {
mNum = 25;
}
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
Log.d(TAG, "onActivityCreated");
if(savedInstanceState != null) {
Log.d(TAG, "saved variable number: " + savedInstanceState.getInt("number"));
}
super.onActivityCreated(savedInstanceState);
}
@Override
public void onSaveInstanceState(Bundle outState) {
Log.d(TAG, "onSaveInstanceState saving: " + mNum);
outState.putInt("number", mNum);
super.onSaveInstanceState(outState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.d(FragmentTabs.TAG, "onCreateView " + (savedInstanceState != null ? ("state: " + savedInstanceState.getInt("number")) : "no state"));
textView = new TextView(getActivity());
textView.setText("Hello world: " + mNum);
textView.setBackgroundDrawable(getResources().getDrawable(android.R.drawable.gallery_thumb));
return textView;
}
}
}
Oto dane wyjściowe pobrane po uruchomieniu tego przykładu, a następnie obróceniu telefonu:
06-11 11:31:42.559: D/FragmentTabs(10726): onTabSelected adding fragment mysimple
06-11 11:31:42.559: D/FragmentTabs(10726): onCreate no state
06-11 11:31:42.559: D/FragmentTabs(10726): onCreateView no state
06-11 11:31:42.567: D/FragmentTabs(10726): onActivityCreated
06-11 11:31:45.286: D/FragmentTabs(10726): onSaveInstanceState saving: 25
06-11 11:31:45.325: D/FragmentTabs(10726): onCreate state 25
06-11 11:31:45.340: D/FragmentTabs(10726): constructor: detaching fragment mysimple
06-11 11:31:45.340: D/FragmentTabs(10726): onTabSelected attaching fragment mysimple
06-11 11:31:45.348: D/FragmentTabs(10726): FragmentTabs.onCreate tab: 0
06-11 11:31:45.348: D/FragmentTabs(10726): FragmentTabs.onCreate number: 0
06-11 11:31:45.348: D/FragmentTabs(10726): onCreateView state: 25
06-11 11:31:45.348: D/FragmentTabs(10726): onActivityCreated
06-11 11:31:45.348: D/FragmentTabs(10726): saved variable number: 25
06-11 11:31:45.348: D/FragmentTabs(10726): onCreateView no state
06-11 11:31:45.348: D/FragmentTabs(10726): onActivityCreated
Moje pytanie brzmi: dlaczego onCreateView i onActivityCreated są wywoływane dwukrotnie? Za pierwszym razem z pakietem z zapisanym stanem, a za drugim z zerowym zapisanym stanem instancji?
Powoduje to problemy z zachowaniem stanu fragmentu podczas obrotu.
Odpowiedzi:
Przez chwilę też drapałem się w głowę, a ponieważ wyjaśnienie Dave'a jest trochę trudne do zrozumienia, opublikuję mój (najwyraźniej działający) kod:
Jak widać, jest to bardzo podobne do przykładu z Androida, poza tym, że nie odłącza się w konstruktorze i używa zamiany zamiast dodawania .
Po wielu próbach i błędach odkryłem, że znalezienie fragmentu w konstruktorze wydaje się sprawiać, że podwójny problem onCreateView w magiczny sposób znika (zakładam, że kończy się on na wartości zerowej dla onTabSelected po wywołaniu przez ścieżkę ActionBar.setSelectedNavigationItem (), gdy stan zapisywania / przywracania).
źródło
add
zamiastreplace
i obrócisz ekran, będziesz miał wiele fragmentów ”onCreateView()
.OK, oto co się dowiedziałem.
Nie rozumiałem, że wszystkie fragmenty, które są dołączone do działania, gdy następuje zmiana konfiguracji (telefon się obraca), są odtwarzane i dodawane z powrotem do działania. (co ma sens)
To, co działo się w konstruktorze TabListener, polegało na odłączaniu karty, jeśli została znaleziona i dołączona do działania. Zobacz poniżej:
W dalszej części czynności onCreate wybrano wcześniej wybraną zakładkę z zapisanego stanu instancji. Zobacz poniżej:
Po wybraniu karty zostanie ona ponownie dołączona do wywołania zwrotnego onTabSelected.
Dołączany fragment jest drugim wywołaniem metod onCreateView i onActivityCreated. (Pierwsza to sytuacja, gdy system odtwarza aktywność i wszystkie dołączone fragmenty) Za pierwszym razem pakiet onSavedInstanceState zapisałby dane, ale nie za drugim razem.
Rozwiązaniem jest nie odłączanie fragmentu w konstruktorze TabListener, po prostu pozostawienie go dołączonego. (Nadal musisz go znaleźć w FragmentManager po jego tagu). Ponadto w metodzie onTabSelected sprawdzam, czy fragment został odłączony, zanim go dołączę. Coś takiego:
źródło
Miałem ten sam problem z prostym działaniem zawierającym tylko jeden fragment (który czasami był zastępowany). Wtedy zdałem sobie sprawę, że używam onSaveInstanceState tylko we fragmencie (i onCreateView do sprawdzania, czy nie ma saveInstanceState), a nie w działaniu.
Po włączeniu urządzenia działanie zawierające fragmenty zostanie zrestartowane i zostanie wywołane onCreated. Tam załączyłem wymagany fragment (który jest poprawny przy pierwszym uruchomieniu).
Na urządzeniu Android najpierw odtworzył widoczny fragment, a następnie wywołał onCreate czynności zawierającej, do której został dołączony mój fragment, zastępując w ten sposób pierwotnie widoczny.
Aby tego uniknąć, po prostu zmieniłem swoją aktywność, aby sprawdzić zapisany stan instancji:
Nie nadpisałem nawet onSaveInstanceState aktywności.
źródło
Dwie przegłosowane odpowiedzi tutaj pokazują rozwiązania dla działania w trybie nawigacji
NAVIGATION_MODE_TABS
, ale miałem ten sam problem z plikiemNAVIGATION_MODE_LIST
. Spowodowało to, że moje fragmenty w niewytłumaczalny sposób straciły swój stan, gdy zmieniła się orientacja ekranu, co było naprawdę denerwujące. Na szczęście dzięki ich pomocnemu kodowi udało mi się to rozgryźć.Zasadniczo, podczas korzystania z nawigacji po liście, `` onNavigationItemSelected ()
is automatically called when your activity is created/re-created, whether you like it or not. To prevent your Fragment's
onCreateView ()from being called twice, this initial automatic call to
onNavigationItemSelected ()should check whether the Fragment is already in existence inside your Activity. If it is, return immediately, because there is nothing to do; if it isn't, then simply construct the Fragment and add it to the Activity like you normally would. Performing this check prevents your Fragment from needlessly being created again, which is what causes
onCreateView () '' ma być wywoływane dwukrotnie!Zobacz moją
onNavigationItemSelected()
implementację poniżej.Inspirację do tego rozwiązania zaczerpnąłem stąd .
źródło
Wydaje mi się, że dzieje się tak dlatego, że za każdym razem tworzysz instancję swojego TabListener ... więc system odtwarza twój fragment z saveInstanceState, a następnie robisz to ponownie w swoim onCreate.
Powinieneś owinąć to w
if(savedInstanceState == null)
tak, aby uruchamiał się tylko wtedy, gdy nie ma pliku saveInstanceState.źródło