Je développe une application utilisant Android 4.0 ICS et des fragments.
Considérez cet exemple modifié de l'exemple d'application de démonstration de l'API ICS 4.0.3 (niveau d'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;
}
}
}
Voici la sortie extraite de l'exécution de cet exemple, puis de la rotation du téléphone:
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
Ma question est la suivante: pourquoi onCreateView et onActivityCreated sont-ils appelés deux fois? La première fois avec un Bundle avec l'état enregistré et la deuxième fois avec un saveInstanceState nul?
Cela pose des problèmes de conservation de l'état du fragment lors de la rotation.
Réponses:
J'étais en train de me gratter la tête à ce sujet pendant un moment aussi, et comme l'explication de Dave est un peu difficile à comprendre, je posterai mon code (qui fonctionne apparemment):
Comme vous pouvez le voir, cela ressemble à peu près à l'exemple Android, mis à part le fait de ne pas se détacher dans le constructeur et d'utiliser replace au lieu d'ajouter .
Après beaucoup de headscratching et d'essais et d'erreurs, j'ai trouvé que trouver le fragment dans le constructeur semble faire disparaître le problème du double onCreateView comme par magie (je suppose qu'il finit par être nul pour onTabSelected lorsqu'il est appelé via le chemin ActionBar.setSelectedNavigationItem () lorsque état de sauvegarde / restauration).
la source
add
placereplace
et faites pivoter l'écran, vous aurez de nombreux fragments »onCreateView()
.Ok, voici ce que j'ai découvert.
Ce que je n'ai pas compris, c'est que tous les fragments associés à une activité lorsqu'un changement de configuration se produit (le téléphone tourne) sont recréés et ajoutés à l'activité. (ce qui a du sens)
Ce qui se passait dans le constructeur TabListener était que l'onglet était détaché s'il était trouvé et attaché à l'activité. Voir ci-dessous:
Plus tard dans l'activité onCreate, l'onglet précédemment sélectionné a été sélectionné dans l'état de l'instance enregistrée. Voir ci-dessous:
Lorsque l'onglet était sélectionné, il était rattaché dans le rappel onTabSelected.
Le fragment attaché est le deuxième appel aux méthodes onCreateView et onActivityCreated. (Le premier étant lorsque le système recrée l'activité et tous les fragments attachés) La première fois que le bundle onSavedInstanceState aurait enregistré des données, mais pas la deuxième fois.
La solution est de ne pas détacher le fragment dans le constructeur TabListener, laissez-le simplement attaché. (Vous devez toujours le trouver dans le FragmentManager par sa balise) De plus, dans la méthode onTabSelected, je vérifie si le fragment est détaché avant de l'attacher. Quelque chose comme ça:
la source
J'ai eu le même problème avec une activité simple ne portant qu'un seul fragment (qui serait parfois remplacé). J'ai alors réalisé que j'utilisais onSaveInstanceState uniquement dans le fragment (et onCreateView pour vérifier savedInstanceState), pas dans l'activité.
Sur l'appareil, l'activité contenant les fragments est redémarrée et onCreated est appelé. Là, j'ai attaché le fragment requis (ce qui est correct au premier démarrage).
Sur l'appareil, Android a tout d'abord recréé le fragment qui était visible, puis appelé onCreate de l'activité contenant où mon fragment était attaché, remplaçant ainsi l'original visible.
Pour éviter cela, j'ai simplement changé mon activité pour vérifier la présence de savedInstanceState:
Je n'ai même pas écrasé onSaveInstanceState de l'activité.
la source
Les deux réponses votées ici montrent des solutions pour une activité avec le mode de navigation
NAVIGATION_MODE_TABS
, mais j'ai eu le même problème avec unNAVIGATION_MODE_LIST
. Cela a fait perdre inexplicablement à mes fragments leur état lorsque l'orientation de l'écran a changé, ce qui était vraiment ennuyeux. Heureusement, grâce à leur code utile, j'ai réussi à le comprendre.Fondamentalement, lorsque vous utilisez une navigation de liste, `` 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 () '' doit être appelé deux fois!Voir ma
onNavigationItemSelected()
mise en œuvre ci-dessous.J'ai emprunté l'inspiration pour cette solution à ici .
la source
Il me semble que c'est parce que vous instanciez votre TabListener à chaque fois ... donc le système recrée votre fragment à partir de savedInstanceState et vous le faites à nouveau dans votre onCreate.
Vous devez encapsuler cela dans un
if(savedInstanceState == null)
afin qu'il ne se déclenche que s'il n'y a pas de savedInstanceState.la source