【问题标题】:Android ViewPageAdapter with a separate backstack for each tabAndroid ViewPageAdapter,每个选项卡都有一个单独的 backstack
【发布时间】:2015-11-12 11:38:24
【问题描述】:

我有一个只有一个 Activity 的 Android 应用。该活动包含一个带有 ViewPageAdapter 的 SlidingTabLayout,如 this 示例中所示。每个选项卡都包含一个相应的根片段,然后在用户使用该屏幕时将其替换为其他片段。如下图所示:

使用我当前的实现,我执行以下操作:转到 tab1,通过将 ProductRootFragment 替换为 ProductListFragment 立即加载 ProductListFragment。我单击一个产品,它将 ProductListFragment 替换为 ProductDetailFragment。现在我切换到 tab2 并且 Tab2ListFragment 通过替换 Tab2RootFragment 立即加载。我单击一个项目,Tab2DetailFragment 替换了 Tab2ListFragment。我现在返回 Tab1,正如我所料,ProductDetailFragment 仍然显示。但是,如果我现在单击后退按钮,它不会像我预期的那样将我带回 ProductListFragment。相反,它会从后台弹出 Tab2DetailFragment。

我想为每个“标签”片段实现一个单独的后台堆栈。已经有几篇关于这个的帖子,但它们似乎都已经过时了,有些人提出了非常复杂的解决方案。 (见hereherehere...)

似乎在最新版本的 SDK 中,Android 可以自动处理这个 backstack 层次结构,而无需在我替换片段的事务中使用 getChildFragmentManager 而不是 getSupportFragmentManager 手动实现某种 backstack。但是,我无法让它发挥作用。 (我直觉上认为正确的解决方案是让 Android 处理这个问题,因为感觉手动覆盖后退按钮并实现自己的堆栈是一种精心设计的 hack。)

有人可以解释如何正确实现为每个选项卡维护单独的后台堆栈的预期行为吗?或者也许链接到一个简单的、精心设计的和最新的示例,说明如何实现所需的行为?

关于我的代码的其他信息: 在我的 MainActivity.java 中,我使用 getSupportFragmentManager() 构造 ViewPagerAdapter。

ViewPagerAdapter.java

@Override
public Fragment getItem(int position) {
    switch(position){
        case 0: return new ProductRootFragment();
        ...
    }
    ...
}

ProductRootFragment.java

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.product_root_fragment, container, false);

    FragmentTransaction transaction = getFragmentManager()
            .beginTransaction();
    transaction.replace(R.id.product_root_container, new ProductListFragment());
    transaction.commit();
    return view;
}

ProductListFragment.java

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    View layout = inflater.inflate(R.layout.product_list_fragment, container, false);
    view = (ProductListView) layout;
    return view;
}
...
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
    ...
    ProductDetailFragment newFragment = new ProductDetailFragment();
    FragmentTransaction transaction = getFragmentManager().beginTransaction();
    transaction.replace(R.id.product_root_container, newFragment);
    transaction.addToBackStack(null);
    transaction.commit();
}

ProductDetailFragment.java

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    view = (ProductDetailView) inflater.inflate(R.layout.product_detail_fragment, container, false);
    return view;
}

【问题讨论】:

    标签: android android-fragments android-viewpager


    【解决方案1】:

    我知道回答这个问题已经很晚了,但也许有人仍然需要它。 为此,您必须在根片段中使用 getChildFragmentManager() 而不是 getFragmentManager()

    例如,我有一个带有 ViewPager 和 TabLayout 的活动,我添加了 RootFragment1RootFragment2 到 ViewPager 并使用 ViewPager 设置 TabLayout。 现在在我的RootFragment1 中替换我的Tab1Fragment1 我使用这个代码:

    final FragmentTransaction ft = getChildFragmentManager().beginTransaction();
    ft.replace(R.id.RootFrame1, Tab1Fragment1.newInstance(), "MyTag");
    ft.commit();
    

    并且需要从后台弹出片段的方法如下:

    public boolean popBackStack() {
        if(getChildFragmentManager().getBackStackEntryCount() > 0) {
            getChildFragmentManager().popBackStackImmediate();
            return true;
        } else
            return false;
    }
    

    例如,我在Tab1Fragment1 中有一个按钮,用当前替换片段Tab1Fragment2。 onClickListener 按钮必须如下所示:

    button1.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            final FragmentTransaction ft = getFragmentManager().beginTransaction();
            ft.replace(R.id.RootFrame1, Tab1Fragment2.newInstance(), "MyTag");
            ft.addToBackStack(null);
            ft.commit();
        }
    });
    

    我对第二个片段 (RootFragment2) 做同样的事情。

    在那之后,现在我只需要覆盖下面的MainActivity 喜欢的onBackPressed()

    @Override
    public void onBackPressed() {
        if (viewPager.getCurrentItem() == 0) {
            RootFragment1 fragment = ((RootFragment1) sectionsPagerAdapter.getItem(0));
            if (!fragment.popBackStack())
                super.onBackPressed();
        } else if (viewPager.getCurrentItem() == 1) {
            RootFragment2 fragment = ((RootFragment2) sectionsPagerAdapter.getItem(1));
            if (!fragment.popBackStack())
                super.onBackPressed();
        } else
            super.onBackPressed();
    }
    

    我检查每个选项卡是否包含片段,然后我从其后台弹出,否则调用super.onBackPressed() 执行默认操作。

    【讨论】:

      【解决方案2】:

      您遇到此问题是因为每次替换片段时都会将该片段添加到后堆栈中。为 Fragment 维护 Backstack 取决于添加 Fragment 的活动。因此,每次您执行addToBackStack() 时,它都会保持相同的活动。 当您尝试使用您的场景时,最后添加的位于 backstack 顶部的片段是 Tab2DetailFragment。 要解决此问题,您需要在每次切换到其他选项卡时清除后台堆栈。 请记住,您需要从 backstack 而不是活动中清除所有片段。

      【讨论】:

      • 这不是真的有用,也不是答案。我理解为什么会出现问题 - 这是因为我不断在单个堆栈顶部添加更多片段,而我添加的最新片段始终位于顶部。当我单击后退按钮时,它们会按照添加顺序从堆栈中弹出,而不管它是从哪个“选项卡”添加的。问题是:如何为每个选项卡实现单独的堆栈,使其正常工作。
      • 查看我提到的这个问题的旧实例的链接。尽管在技术上只有一个 backstack,但这些示例表明确实可以模拟行为,就好像存在不同的 backstack。问题是这些示例已经过时并且没有正确解释 getChildFragmentManager 的正确使用,这似乎是模拟这种行为的正确方法。我的问题要求提供解决方案以使此功能正常工作。
      • 我遇到了完全相同的问题。当切换到导致第一个根片段被破坏的选项卡时(因为我正在使用 FragmentStateAdapter),当我按下后退按钮时出现崩溃,因为不再找到视图。不知道为什么SDK没有实现这个。
      【解决方案3】:

      你可以使用下面的代码;

      @Override
      public void onBackPressed() {
      
          //get current tab index.
          if (viewPager.getCurrentItem() == 1) {
              viewPager.setCurrentItem(0);
          } else if (viewPager.getCurrentItem() == 2) {
              viewPager.setCurrentItem(0);
          } else if (viewPager.getCurrentItem() == 3) {
              viewPager.setCurrentItem(0);
          } else {
              super.onBackPressed();
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2023-04-09
        • 2014-01-27
        • 1970-01-01
        • 2019-04-30
        • 1970-01-01
        • 2015-01-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多