Separate Back Stack for each tab in BottomNavigationView Android using Fragments

前端 未结 5 2023
梦毁少年i
梦毁少年i 2020-12-13 07:27

I\'m implementing BottomNavigationView for navigation in an Android app. I am using Fragments to set the content for each tab.

I know how to set up one

相关标签:
5条回答
  • 2020-12-13 07:30

    This behavior is supported by the new Navigation Architecture Component (https://developer.android.com/topic/libraries/architecture/navigation/).

    Essentially, one can use NavHostFragment, which is a fragment that controls its own back stack:

    Each NavHostFragment has a NavController that defines valid navigation within the navigation host. This includes the navigation graph as well as navigation state such as current location and back stack that will be saved and restored along with the NavHostFragment itself. https://developer.android.com/reference/androidx/navigation/fragment/NavHostFragment

    Here is an example: https://github.com/deisold/navigation


    Edit: Turns out Navigation Architecture Component doesn't support seperate back stacks anyway, as pointed out by the commenters. But as @r4jiv007 mentioned, they are working on it and has offered an "official hack" in the meantime: https://github.com/googlesamples/android-architecture-components/tree/master/NavigationAdvancedSample

    0 讨论(0)
  • 2020-12-13 07:32

    Suppose you have 5(A, B, C, D, E) BottomNavigationView menu item, then in Activity create 5 FrameLayout(frmlytA, frmlytB, frmlytC, frmlytD, frmlytE) in parallel overlapping manner as the container for each of these menu items. When BottomNavigation Menu item A is pressed then hide all the other FrameLayouts(Visibility = GONE) and just show(Visibility = VISIBLE) the FrameLayout 'frmlytA' which will host the FragmentA and over this container do the further transactions like (FragmentA -> FragmentX -> FragmentY). And then If user clicks BottomNavigation Menu item B then just hide this(frmlytA) container and show 'frmlytB'. Then if user again presses the menu item A then show 'frmlytA' it should retain the earlier state. So, like this you can switch between the container FrameLayouts and can maintain the back stack of each container.

    0 讨论(0)
  • 2020-12-13 07:38

    Instead of using replace method use add fragment,

    Instead of this method ft.replace(R.id.content, selectedFragment);

    Use this ft.add(R.id.content, selectedFragment);

        Fragment selectedFragment = ItemsFragment.newInstance();
        FragmentTransaction ft = getFragmentManager().beginTransaction();
        ft.(R.id.content, selectedFragment);
        ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
        ft.addToBackStack(null);
        ft.commit();
    
    0 讨论(0)
  • 2020-12-13 07:39

    Finally, I found the solution, it was inspired by a previous answer on StackOverflow: Separate Back Stack for each tab in Android using Fragments
    I only have replaced TabHost with BottomNavigationView and here is the code:
    Main Activity

    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();
    }
    

    }

    Home fragment example

    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;
    }
    

    }

    0 讨论(0)
  • 2020-12-13 07:45

    It is worth noting that the behavior you describe goes against the Google guidelines. https://material.io/guidelines/components/bottom-navigation.html#bottom-navigation-behavior

    Navigation through the bottom navigation bar should reset the task state.

    In other words, having Fragment A and Fragment B "inside" Tab 1 is fine, but if the user opens Fragment B, clicks Tab 2, and then clicks Tab 1 again, they should see Fragment A.

    0 讨论(0)
提交回复
热议问题