Ich entwickle eine App mit Android 4.0 ICS und Fragmenten.
Betrachten Sie dieses modifizierte Beispiel aus der Demo-Beispiel-App der ICS 4.0.3 (API Level 15) API:
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;
}
}
}}
Hier ist die Ausgabe, die beim Ausführen dieses Beispiels und beim anschließenden Drehen des Telefons abgerufen wurde:
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
Meine Frage ist, warum onCreateView und onActivityCreated zweimal aufgerufen werden. Das erste Mal mit einem Bundle mit dem gespeicherten Status und das zweite Mal mit einem null savedInstanceState?
Dies verursacht Probleme beim Beibehalten des Zustands des Fragments bei Rotation.
Antworten:
Ich habe mir auch eine Weile am Kopf gekratzt, und da Daves Erklärung etwas schwer zu verstehen ist, werde ich meinen (anscheinend funktionierenden) Code veröffentlichen:
Wie Sie sehen können, ähnelt es dem Android-Beispiel, abgesehen davon, dass es sich nicht im Konstruktor löst und anstelle von Hinzufügen Ersetzen verwendet .
Nach langem Headscratching und Versuch und Irrtum stellte ich fest, dass das Finden des Fragments im Konstruktor das doppelte onCreateView-Problem auf magische Weise verschwinden lässt (ich gehe davon aus, dass es für onTabSelected nur null ist, wenn es über den Pfad ActionBar.setSelectedNavigationItem () aufgerufen wird, wenn Status speichern / wiederherstellen).
quelle
add
anstelle vonreplace
und drehen Sie den Bildschirm, haben Sie viele FragmenteonCreateView()
.Ok, hier ist was ich herausgefunden habe.
Was ich nicht verstanden habe, ist, dass alle Fragmente, die an eine Aktivität angehängt werden, wenn eine Konfigurationsänderung stattfindet (Telefon dreht sich), neu erstellt und der Aktivität wieder hinzugefügt werden. (was Sinn macht)
Im TabListener-Konstruktor wurde die Registerkarte getrennt, wenn sie gefunden und an die Aktivität angehängt wurde. Siehe unten:
Später in der Aktivität onCreate wurde die zuvor ausgewählte Registerkarte aus dem Status der gespeicherten Instanz ausgewählt. Siehe unten:
Wenn die Registerkarte ausgewählt wurde, wurde sie im onTabSelected-Rückruf erneut zugeordnet.
Das angehängte Fragment ist der zweite Aufruf der Methoden onCreateView und onActivityCreated. (Das erste Mal, wenn das System die Aktivität und alle angehängten Fragmente neu erstellt.) Das erste Mal hätte das onSavedInstanceState-Bundle Daten gespeichert, aber nicht das zweite Mal.
Die Lösung besteht darin, das Fragment nicht im TabListener-Konstruktor zu trennen, sondern nur angehängt zu lassen. (Sie müssen es immer noch im FragmentManager anhand seines Tags finden.) Außerdem überprüfe ich in der onTabSelected-Methode, ob das Fragment getrennt ist, bevor ich es anhänge. Etwas wie das:
quelle
Ich hatte das gleiche Problem mit einer einfachen Aktivität, die nur ein Fragment trug (das manchmal ersetzt wurde). Dann wurde mir klar, dass ich onSaveInstanceState nur im Fragment (und onCreateView, um nach savedInstanceState zu suchen) und nicht in der Aktivität verwende.
Beim Einschalten des Geräts wird die Aktivität mit den Fragmenten neu gestartet und onCreated wird aufgerufen. Dort habe ich das gewünschte Fragment angehängt (was beim ersten Start richtig ist).
Auf dem Gerät wird Android zuerst das sichtbare Fragment neu erstellt und dann onCreate der enthaltenen Aktivität aufgerufen, an die mein Fragment angehängt wurde, wodurch das ursprünglich sichtbare ersetzt wird.
Um dies zu vermeiden, habe ich einfach meine Aktivität geändert, um nach savedInstanceState zu suchen:
Ich habe nicht einmal onSaveInstanceState der Aktivität überschrieben.
quelle
Die beiden positiv bewerteten Antworten hier zeigen Lösungen für eine Aktivität mit Navigationsmodus
NAVIGATION_MODE_TABS
, aber ich hatte das gleiche Problem mit aNAVIGATION_MODE_LIST
. Dadurch verloren meine Fragmente unerklärlicherweise ihren Zustand, als sich die Bildschirmausrichtung änderte, was wirklich ärgerlich war. Zum Glück konnte ich es aufgrund ihres hilfreichen Codes herausfinden.Grundsätzlich muss bei Verwendung einer Listennavigation "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 ()" zweimal aufgerufen werden!Siehe meine
onNavigationItemSelected()
Implementierung unten.Ich lieh mir Inspiration für diese Lösung von hier .
quelle
Für mich sieht es so aus, als ob Sie Ihren TabListener jedes Mal instanziieren ... also erstellt das System Ihr Fragment aus dem savedInstanceState neu und dann tun Sie es erneut in Ihrem onCreate.
Sie sollten das in a
if(savedInstanceState == null)
einschließen, damit es nur ausgelöst wird, wenn kein savedInstanceState vorhanden ist.quelle