web-dev-qa-db-fra.com

Séparez la pile arrière pour chaque onglet de BottomNavigationView Android à l'aide de Fragments

J'implémente BottomNavigationView pour la navigation dans une application Android. J'utilise des fragments pour définir le contenu de chaque onglet.

Je sais comment configurer un fragment pour chaque onglet, puis en changer lorsque vous cliquez sur un onglet. Mais comment puis-je avoir une pile arrière distincte pour chaque onglet? Voici le code pour configurer un fragment:

Fragment selectedFragment = ItemsFragment.newInstance();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.content, selectedFragment);
transaction.commit();

Par exemple, Fragment A et B seraient sous l'onglet 1 et Fragment C et D sous l'onglet 2. Lorsque l'application est démarrée, le fragment A est affiché et l'onglet 1 est sélectionné. Ensuite, Fragment A pourrait être remplacé par le fragment B. Lorsque l'onglet 2 est sélectionné, le fragment C doit être affiché. Si l'onglet 1 est alors sélectionné, FragmentB devrait à nouveau être affiché. À ce stade, il devrait être possible d’utiliser le bouton Précédent pour afficher le fragment A.

Et voici le code pour configurer la prochaine variable fragment dans le même onglet:

Fragment selectedFragment = ItemsFragment.newInstance();
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.content, selectedFragment);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.addToBackStack(null);
ft.commit();
13
Rita Azar

Enfin, j'ai trouvé la solution, inspirée d'une réponse précédente sur StackOverflow: Séparer la pile arrière pour chaque onglet sous Android à l'aide de Fragments
J'ai seulement remplacé TabHost par BottomNavigationView et voici le code:
Activité principale

public class MainActivity extends AppCompatActivity {

private HashMap<String, Stack<Fragment>> mStacks;
public static final String TAB_HOME  = "tab_home";
public static final String TAB_DASHBOARD  = "tab_dashboard";
public static final String TAB_NOTIFICATIONS  = "tab_notifications";

private String mCurrentTab;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.navigation);
    navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);

    mStacks = new HashMap<String, Stack<Fragment>>();
    mStacks.put(TAB_HOME, new Stack<Fragment>());
    mStacks.put(TAB_DASHBOARD, new Stack<Fragment>());
    mStacks.put(TAB_NOTIFICATIONS, new Stack<Fragment>());

    navigation.setSelectedItemId(R.id.navigation_home);
}

private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
        = new BottomNavigationView.OnNavigationItemSelectedListener() {

    @Override
    public boolean onNavigationItemSelected(@NonNull MenuItem item) {
        switch (item.getItemId()) {
            case R.id.navigation_home:
                selectedTab(TAB_HOME);
                return true;
            case R.id.navigation_dashboard:
                selectedTab(TAB_DASHBOARD);
                return true;
            case R.id.navigation_notifications:
                selectedTab(TAB_NOTIFICATIONS);
                return true;
        }
        return false;
    }

};

private void gotoFragment(Fragment selectedFragment)
{
    FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
    fragmentTransaction.replace(R.id.content, selectedFragment);
    fragmentTransaction.commit();
}

private void selectedTab(String tabId)
{
    mCurrentTab = tabId;

    if(mStacks.get(tabId).size() == 0){
      /*
       *    First time this tab is selected. So add first fragment of that tab.
       *    Dont need animation, so that argument is false.
       *    We are adding a new fragment which is not present in stack. So add to stack is true.
       */
        if(tabId.equals(TAB_HOME)){
            pushFragments(tabId, new HomeFragment(),true);
        }else if(tabId.equals(TAB_DASHBOARD)){
            pushFragments(tabId, new DashboardFragment(),true);
        }else if(tabId.equals(TAB_NOTIFICATIONS)){
            pushFragments(tabId, new NotificationsFragment(),true);
        }
    }else {
      /*
       *    We are switching tabs, and target tab is already has atleast one fragment.
       *    No need of animation, no need of stack pushing. Just show the target fragment
       */
        pushFragments(tabId, mStacks.get(tabId).lastElement(),false);
    }
}

public void pushFragments(String tag, Fragment fragment, boolean shouldAdd){
    if(shouldAdd)
        mStacks.get(tag).Push(fragment);
    FragmentManager manager = getSupportFragmentManager();
    FragmentTransaction ft = manager.beginTransaction();
    ft.replace(R.id.content, fragment);
    ft.commit();
}

public void popFragments(){
  /*
   *    Select the second last fragment in current tab's stack..
   *    which will be shown after the fragment transaction given below
   */
    Fragment fragment = mStacks.get(mCurrentTab).elementAt(mStacks.get(mCurrentTab).size() - 2);

  /*pop current fragment from stack.. */
    mStacks.get(mCurrentTab).pop();

  /* We have the target fragment in hand.. Just show it.. Show a standard navigation animation*/
    FragmentManager manager = getSupportFragmentManager();
    FragmentTransaction ft = manager.beginTransaction();
    ft.replace(R.id.content, fragment);
    ft.commit();
}

@Override
public void onBackPressed() {
    if(mStacks.get(mCurrentTab).size() == 1){
        // We are already showing first fragment of current tab, so when back pressed, we will finish this activity..
        finish();
        return;
    }

    /* Goto previous fragment in navigation stack of this tab */
    popFragments();
}

}

Exemple de fragment de maison

public class HomeFragment extends Fragment {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_home, container, false);
    Button gotoNextFragment = (Button) view.findViewById(R.id.gotoHome2);

    gotoNextFragment.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            ((MainActivity)getActivity()).pushFragments(MainActivity.TAB_HOME, new Home2Fragment(),true);
        }
    });
    return view;
}

}

22
Rita Azar

Ce comportement est pris en charge par le nouveau composant d'architecture de navigation ( https://developer.Android.com/topic/libraries/architecture/navigation/ ).

Essentiellement, on peut utiliser NavHostFragment, qui est un fragment qui contrôle sa propre pile arrière:

Chaque NavHostFragment a un NavController qui définit une navigation valide dans l'hôte de navigation. Cela inclut le graphique de navigation ainsi que l'état de navigation tel que l'emplacement actuel et la pile arrière qui seront sauvegardés et restaurés avec le NavHostFragment lui-même. https://developer.Android.com/reference/androidx/navigation/fragment/NavHostFragment

Voici un exemple: https://github.com/deisold/navigation

2
Mikael

Il est à noter que le comportement que vous décrivez va à l'encontre des consignes de Google. https://material.io/guidelines/components/bottom-navigation.html#bottom-navigation-behavior

La navigation dans la barre de navigation inférieure devrait réinitialiser l'état de la tâche.

En d'autres termes, avoir le fragment A et le fragment B "dans" l'onglet 1, c'est bien, mais si l'utilisateur ouvre le fragment B, clique sur l'onglet 2, puis à nouveau sur l'onglet 1, il doit voir le fragment A.

2
Ben P.

Supposons que vous ayez 5 éléments de menu (A, B, C, D, E) BottomNavigationView, puis dans Activity, créez 5 FrameLayout (frmlytA, frmlytB, frmlytB, frmlytD, frmlytE) de manière à se chevaucher parallèlement comme conteneur de chacun de ces éléments de menu. Lorsque vous appuyez sur l’élément A du menu BottomNavigation, masquez tous les autres FrameLayouts (Visibility = GONE) et affichez simplement (Visibility = VISIBLE) le FrameLayout 'frmlytA' qui hébergera le FragmentA et sur ce conteneur effectuera les transactions ultérieures telles que (FragmentA -> FragmentX -> FragmentY). Et puis, si l'utilisateur clique sur l'élément B du menu BottomNavigation, il suffit de masquer ce conteneur (frmlytA) et d'afficher «frmlytB». Ensuite, si l'utilisateur appuie à nouveau sur l'élément de menu A, puis affiche «frmlytA», il doit conserver l'état antérieur. Ainsi, comme cela, vous pouvez basculer entre les FrameLayouts du conteneur et conserver la pile arrière de chaque conteneur.

0
mithil1501