J'apprends à utiliser des fragments. J'ai trois instances de Fragment
qui sont initialisées en haut de la classe. J'ajoute le fragment à une activité comme celle-ci:
Déclarer et initialiser:
Fragment A = new AFragment();
Fragment B = new BFragment();
Fragment C = new CFragment();
Remplacement/Ajout:
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, A);
ft.addToBackStack(null);
ft.commit();
Ces extraits fonctionnent correctement. Chaque fragment est attaché à l'activité et est sauvegardé dans la pile arrière sans aucun problème.
Ainsi, lorsque je lance A
, C
, puis B
, la pile ressemble à ceci:
| |
|B|
|C|
|A|
___
Et lorsque j'appuie sur le bouton 'retour', B
est détruit et C
est repris.
Mais, lorsque je lance le fragment A
une seconde fois, au lieu de le reprendre à partir de la pile arrière, il est ajouté en haut de la pile arrière.
| |
|A|
|C|
|A|
___
Mais je veux reprendre A
et détruire tous les fragments qui s’y trouvent (le cas échéant). En fait, j'aime bien le comportement par défaut de la pile arrière.
Comment puis-je accomplir cela?
Attendu: (A
devrait être repris et les fragments supérieurs devraient être détruits)
| |
| |
| |
|A|
___
Edit: (suggéré par A - C)
Ceci est mon code d'essai:
private void selectItem(int position) {
Fragment problemSearch = null, problemStatistics = null;
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
String backStateName = null;
Fragment fragmentName = null;
boolean fragmentPopped = false;
switch (position) {
case 0:
fragmentName = profile;
break;
case 1:
fragmentName = submissionStatistics;
break;
case 2:
fragmentName = solvedProblemLevel;
break;
case 3:
fragmentName = latestSubmissions;
break;
case 4:
fragmentName = CPExercise;
break;
case 5:
Bundle bundle = new Bundle();
bundle.putInt("problem_no", problemNo);
problemSearch = new ProblemWebView();
problemSearch.setArguments(bundle);
fragmentName = problemSearch;
break;
case 6:
fragmentName = rankList;
break;
case 7:
fragmentName = liveSubmissions;
break;
case 8:
Bundle bundles = new Bundle();
bundles.putInt("problem_no", problemNo);
problemStatistics = new ProblemStatistics();
problemStatistics.setArguments(bundles);
fragmentName = problemStatistics;
default:
break;
}
backStateName = fragmentName.getClass().getName();
fragmentPopped = manager.popBackStackImmediate(backStateName, 0);
if (!fragmentPopped) {
ft.replace(R.id.content_frame, fragmentName);
}
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.addToBackStack(backStateName);
ft.commit();
// I am using drawer layout
mDrawerList.setItemChecked(position, true);
setTitle(title[position]);
mDrawerLayout.closeDrawer(mDrawerList);
}
Le problème est que, lorsque je lance A
puis B
, puis appuyez sur 'retour', B
est supprimé et A
est repris. et en appuyant sur 'back' une deuxième fois devrait quitter l'application. Mais il affiche une fenêtre vierge et je dois appuyer une troisième fois pour la fermer.
Aussi, quand je lance A
, puis B
, puis C
, puis B
encore ...
Attendu:
| |
| |
|B|
|A|
___
Réel:
| |
|B|
|B|
|A|
___
Devrais-je remplacer onBackPressed()
par une personnalisation ou est-ce que je manque quelque chose?
En lisant le documentation , il existe un moyen de faire apparaître la pile arrière en fonction du nom de la transaction ou de l'id fourni par commit. Il est plus facile d’utiliser le nom peut car il n’est pas nécessaire de garder la trace d’un nombre susceptible de changer et de renforcer la logique «entrée unique de la pile arrière».
Puisque vous ne voulez qu'une seule entrée de pile arrière par Fragment
, définissez le nom d'état arrière comme nom de classe du fragment (via getClass().getName()
). Ensuite, lors du remplacement d'une Fragment
, utilisez la méthode popBackStackImmediate()
. Si elle retourne la valeur true, cela signifie qu'il existe une instance du fragment dans la pile arrière. Sinon, exécutez réellement la logique de remplacement de fragment.
private void replaceFragment (Fragment fragment){
String backStateName = fragment.getClass().getName();
FragmentManager manager = getSupportFragmentManager();
boolean fragmentPopped = manager.popBackStackImmediate (backStateName, 0);
if (!fragmentPopped){ //fragment not in back stack, create it.
FragmentTransaction ft = manager.beginTransaction();
ft.replace(R.id.content_frame, fragment);
ft.addToBackStack(backStateName);
ft.commit();
}
}
MODIFIER
Le problème est - quand je lance A puis B, puis appuie sur le bouton de retour, B est retiré et A est repris. et en appuyant à nouveau sur le bouton retour devrait quitter l'application. Mais il affiche une fenêtre vierge et nécessite une autre presse pour la fermer.
En effet, la variable FragmentTransaction
est ajoutée à la pile arrière pour garantir que nous pourrons faire apparaître les fragments par-dessus ultérieurement. Une solution rapide consiste à remplacer onBackPressed()
et à terminer l'activité si la pile arrière ne contient que 1 Fragment
@Override
public void onBackPressed(){
if (getSupportFragmentManager().getBackStackEntryCount() == 1){
finish();
}
else {
super.onBackPressed();
}
}
En ce qui concerne les entrées de la pile de retour dupliquées, votre instruction conditionnelle qui remplace le fragment s'il n'a pas été sauté est clairement différente que celle de mon extrait de code d'origine. Ce que vous faites est d’ajouter à la pile arrière, que cette pile ait été ou non détruite.
Quelque chose comme ça devrait être plus proche de ce que vous voulez:
private void replaceFragment (Fragment fragment){
String backStateName = fragment.getClass().getName();
String fragmentTag = backStateName;
FragmentManager manager = getSupportFragmentManager();
boolean fragmentPopped = manager.popBackStackImmediate (backStateName, 0);
if (!fragmentPopped && manager.findFragmentByTag(fragmentTag) == null){ //fragment not in back stack, create it.
FragmentTransaction ft = manager.beginTransaction();
ft.replace(R.id.content_frame, fragment, fragmentTag);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.addToBackStack(backStateName);
ft.commit();
}
}
La condition a été légèrement modifiée, car la sélection du même fragment alors qu’il était visible provoquait également des entrées en double.
La mise en oeuvre:
Je suggère fortement de ne pas supprimer la méthode replaceFragment()
mise à jour comme vous l'avez fait dans votre code. Toute la logique est contenue dans cette méthode et le déplacement des pièces peut poser problème.
Cela signifie que vous devriez copier la méthode replaceFragment()
mise à jour dans votre classe, puis changer
backStateName = fragmentName.getClass().getName();
fragmentPopped = manager.popBackStackImmediate(backStateName, 0);
if (!fragmentPopped) {
ft.replace(R.id.content_frame, fragmentName);
}
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.addToBackStack(backStateName);
ft.commit();
c'est tout simplement
replaceFragment (fragmentName);
EDIT # 2
Pour mettre à jour le tiroir lorsque la pile arrière est modifiée, créez une méthode qui accepte un fragment et compare les noms de classe. Si quelque chose correspond, changez le titre et la sélection. Ajoutez également une OnBackStackChangedListener
et faites-la appeler votre méthode de mise à jour s'il existe un fragment valide.
Par exemple, dans l'activité onCreate()
, ajoutez
getSupportFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener() {
@Override
public void onBackStackChanged() {
Fragment f = getSupportFragmentManager().findFragmentById(R.id.content_frame);
if (f != null){
updateTitleAndDrawer (f);
}
}
});
Et l'autre méthode:
private void updateTitleAndDrawer (Fragment fragment){
String fragClassName = fragment.getClass().getName();
if (fragClassName.equals(A.class.getName())){
setTitle ("A");
//set selected item position, etc
}
else if (fragClassName.equals(B.class.getName())){
setTitle ("B");
//set selected item position, etc
}
else if (fragClassName.equals(C.class.getName())){
setTitle ("C");
//set selected item position, etc
}
}
Désormais, chaque fois que la pile arrière change, le titre et la position cochée refléteront la variable Fragment
visible.
Je pense que cette méthode peut résoudre votre problème:
public static void attachFragment ( int fragmentHolderLayoutId, Fragment fragment, Context context, String tag ) {
FragmentManager manager = ( (AppCompatActivity) context ).getSupportFragmentManager ();
manager.findFragmentByTag ( tag );
FragmentTransaction ft = manager.beginTransaction ();
if (manager.findFragmentByTag ( tag ) == null) { // No fragment in backStack with same tag..
ft.add ( fragmentHolderLayoutId, fragment, tag );
ft.addToBackStack ( tag );
ft.commit ();
}
else {
ft.show ( manager.findFragmentByTag ( tag ) ).commit ();
}
}
qui a été à l'origine posté dans Cette question
Étape 1: Implémenter une interface avec votre classe d'activité
public class AuthenticatedMainActivity extends Activity implements FragmentManager.OnBackStackChangedListener{
@Override
protected void onCreate(Bundle savedInstanceState) {
.............
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction().add(R.id.frame_container,fragment, "First").addToBackStack(null).commit();
}
private void switchFragment(Fragment fragment){
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.frame_container, fragment).addToBackStack("Tag").commit();
}
@Override
public void onBackStackChanged() {
FragmentManager fragmentManager = getFragmentManager();
System.out.println("@Class: SummaryUser : onBackStackChanged "
+ fragmentManager.getBackStackEntryCount());
int count = fragmentManager.getBackStackEntryCount();
// when a fragment come from another the status will be zero
if(count == 0){
System.out.println("again loading user data");
// reload the page if user saved the profile data
if(!objPublicDelegate.checkNetworkStatus()){
objPublicDelegate.showAlertDialog("Warning"
, "Please check your internet connection");
}else {
objLoadingDialog.show("Refreshing data...");
mNetworkMaster.runUserSummaryAsync();
}
// IMPORTANT: remove the current fragment from stack to avoid new instance
fragmentManager.removeOnBackStackChangedListener(this);
}// end if
}
}
Étape 2: Lorsque vous appelez un autre fragment, ajoutez cette méthode:
String backStateName = this.getClass().getName();
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.addOnBackStackChangedListener(this);
Fragment fragmentGraph = new GraphFragment();
Bundle bundle = new Bundle();
bundle.putString("graphTag", view.getTag().toString());
fragmentGraph.setArguments(bundle);
fragmentManager.beginTransaction()
.replace(R.id.content_frame, fragmentGraph)
.addToBackStack(backStateName)
.commit();
Une solution plus simple changera cette ligne
ft.replace(R.id.content_frame, A);
__ft.add(R.id.content_frame, A);
Et dans votre mise en page XML, veuillez utiliser
Android:background="@color/white"
Android:clickable="true"
Android:focusable="true"
Clickable
signifie qu'il peut être cliqué par un périphérique de pointeur ou touché par un périphérique tactile.
Focusable
signifie qu'il peut obtenir le focus d'un périphérique d'entrée tel qu'un clavier. Les périphériques d'entrée tels que les claviers ne peuvent pas choisir la vue à laquelle envoyer leurs événements d'entrée en fonction des entrées elles-mêmes. Ils les envoient donc à la vue active.
Je sais qu'il est assez tard pour répondre à cette question, mais j'ai résolu ce problème moi-même et j'ai pensé qu'il valait la peine de le partager avec tout le monde. »
public void replaceFragment(BaseFragment fragment) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
final FragmentManager fManager = getSupportFragmentManager();
BaseFragment fragm = (BaseFragment) fManager.findFragmentByTag(fragment.getFragmentTag());
transaction.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_left, R.anim.enter_from_left, R.anim.exit_to_right);
if (fragm == null) { //here fragment is not available in the stack
transaction.replace(R.id.container, fragment, fragment.getFragmentTag());
transaction.addToBackStack(fragment.getFragmentTag());
} else {
//fragment was found in the stack , now we can reuse the fragment
// please do not add in back stack else it will add transaction in back stack
transaction.replace(R.id.container, fragm, fragm.getFragmentTag());
}
transaction.commit();
}
Et dans le onBackPressed ()
@Override
public void onBackPressed() {
if(getSupportFragmentManager().getBackStackEntryCount()>1){
super.onBackPressed();
}else{
finish();
}
}
getFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
@Override
public void onBackStackChanged() {
if(getFragmentManager().getBackStackEntryCount()==0) {
onResume();
}
}
});