使用片段为BottomNavigationView Android中的每个选项卡分隔Back Stack

时间:2017-08-10 10:34:13

标签: android android-fragments bottomnavigationview fragment-backstack

我在Android应用中实施导航BottomNavigationView。我正在使用片段为每个标签设置内容。

我知道如何为每个标签设置一个片段,然后在单击标签时切换片段。但是,如何为每个选项卡分别设置一个后备堆栈? 以下是设置一个片段的代码:

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

例如,Fragment A和B将位于选项卡1下,Fragment C和D位于选项卡2下。当应用程序启动时,将显示片段A并选择选项卡1。然后Fragment A可能被片段B替换。当选择标签2时,应显示片段C.如果然后选择了标签1,则应再次显示Fragment B.此时,应该可以使用后退按钮显示片段A.

以下是在同一标签中设置下一个fragment的代码:

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

5 个答案:

答案 0 :(得分:23)

最后,我找到了解决方案,它的灵感来自之前对StackOverflow的回答:Separate Back Stack for each tab in Android using Fragments
我只用BottomNavigationView替换了TabHost,这里是代码:
主要活动

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

}

主页片段示例

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

}

答案 1 :(得分:2)

值得注意的是,您描述的行为违反了Google准则。 https://material.io/guidelines/components/bottom-navigation.html#bottom-navigation-behavior

  

通过底部导航栏导航应重置任务状态。

换句话说,将片段A和片段B放在“内部”选项卡1中就可以了,但是如果用户打开片段B,单击Tab 2,然后再次单击Tab 1,它们应该看到片段A.

答案 2 :(得分:2)

新的导航体系结构组件(https://developer.android.com/topic/libraries/architecture/navigation/)支持此行为。

本质上,人们可以使用NavHostFragment,这是一个控制自己的后堆栈的片段:

  

每个NavHostFragment都有一个NavController,用于定义导航主机中的有效导航。这包括导航图以及导航状态(例如当前位置和后退堆栈),它们将与NavHostFragment本身一起保存和恢复。   https://developer.android.com/reference/androidx/navigation/fragment/NavHostFragment

这里是一个示例:https://github.com/deisold/navigation

答案 3 :(得分:0)

假设您有5(A,B,C,D,E)BottomNavigationView菜单项,然后在Activity中以平行重叠的方式创建5 FrameLayout(frmlytA,frmlytB,frmlytC,frmlytD,frmlytE)作为这些容器的容器菜单项。当按下BottomNavigation菜单项A时,隐藏所有其他FrameLayouts(可见性= GONE),然后仅显示(Visibility = VISIBLE)将托管FragmentA的FrameLayout'frmlytA',并在此容器上进行进一步的事务,例如(FragmentA-> FragmentX -> FragmentY)。然后,如果用户单击BottomNavigation菜单项B,则只需隐藏此(frmlytA)容器并显示'frmlytB'。然后,如果用户再次按下菜单项A,然后显示“ frmlytA”,则它应保持较早的状态。因此,您可以像这样在容器的FrameLayouts之间切换,并可以维护每个容器的后堆栈。

答案 4 :(得分:-4)

使用add fragment,

而不是使用替换方法

而不是这种方法 ft.replace(R.id.content,selectedFragment);

使用此 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();