android - 如何从 BackStack 恢复现有的 Fragment

标签 android android-fragments back-stack

我正在学习如何使用 fragment 。我有三个在类顶部初始化的 Fragment 实例。我将 fragment 添加到这样的 Activity 中:

声明和初始化:

Fragment A = new AFragment();
Fragment B = new BFragment();
Fragment C = new CFragment();

替换/添加:

FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, A);
ft.addToBackStack(null);
ft.commit();

这些代码段运行正常。每个 fragment 都附加到 Activity 中,并毫无问题地保存到后台堆栈。

所以当我启动 AC,然后是 B 时,堆栈如下所示:

| |
|B|
|C|
|A|
___

当我按下“返回”按钮时,B 被销毁,C 被恢复。

但是,当我第二次启动 fragment A 时,它不是从后台堆栈恢复,而是添加到后台堆栈的顶部

| |
|A|
|C|
|A|
___

但我想恢复 A 并销毁它上面的所有 fragment (如果有的话)。实际上,我只是喜欢默认的回栈行为。

我该如何做到这一点?

预期:(A 应恢复,顶部 fragment 应销毁)

| |
| |
| |
|A|
___

编辑:(由 A--C 建议)

这是我尝试的代码:

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

问题是,当我启动 A 然后 B,然后按“返回”,B 被删除,A 恢复。并再次按“返回”应退出应用程序。但它显示一个空白窗口,我必须第三次按下才能关闭它。

另外,当我启动 A,然后是 B,然后是 C,然后是 B...

预期:

| |
| |
|B|
|A|
___

实际:

| |
|B|
|B|
|A|
___

我应该用任何自定义覆盖 onBackPressed() 还是我遗漏了什么?

最佳答案

阅读documentation ,有一种方法可以根据事务名称或提交提供的 id 弹出回栈。使用名称​​可能会更容易,因为它不需要跟踪可能更改的数字并强化“唯一的返回堆栈条目”逻辑。

由于您只希望每个 Fragment 有一个返回堆栈条目,因此将返回状态名称设为 Fragment 的类名(通过 getClass().getName())。然后在替换 Fragment 时,使用 popBackStackImmediate()方法。如果它返回 true,则意味着在后堆栈中有一个 Fragment 的实例。如果没有,实际执行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();
  }
}

编辑

The problem is - when i launch A and then B, then press back button, B is removed and A is resumed. and pressing again back button should exit the app. But it is showing a blank window and need another press to close it.

这是因为 FragmentTransaction 被添加到后栈中,以确保我们以后可以将 fragment 弹出到顶部。如果返回堆栈仅包含 1 个 Fragment

,则快速解决此问题的方法是覆盖 onBackPressed() 并完成 Activity
@Override
public void onBackPressed(){
  if (getSupportFragmentManager().getBackStackEntryCount() == 1){
    finish();
  }
  else {
    super.onBackPressed();
  }
}

关于重复的返回堆栈条目,如果 fragment 尚未弹出,则替换 fragment 的条件语句与我的原始代码 fragment 明显不同。您正在做的是添加到后台堆栈,无论后台堆栈是否被弹出。

这样的东西应该更接近你想要的:

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

条件有所改变,因为在可见时选择相同的 fragment 也会导致重复条目。

实现:

我强烈建议不要像在代码中那样将更新后的 replaceFragment() 方法分开。所有逻辑都包含在此方法中,移动部件可能会导致问题。

这意味着您应该将更新后的 replaceFragment() 方法复制到您的类中,然后更改

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

原来如此

replaceFragment (fragmentName);

编辑#2

要在返回堆栈更改时更新抽屉,请创建一个接受 Fragment 并比较类名的方法。如果有任何匹配,请更改标题和选择。还添加一个 OnBackStackChangedListener 并让它调用你的更新方法,如果有一个有效的 fragment 。

例如在Activity的onCreate()中,添加

getSupportFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener() {

  @Override
  public void onBackStackChanged() {
    Fragment f = getSupportFragmentManager().findFragmentById(R.id.content_frame);
    if (f != null){
      updateTitleAndDrawer (f);
    }

  }
});

还有另一种方法:

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

现在,每当返回堆栈发生变化时,标题和检查位置都会反射(reflect)可见的Fragment

关于android - 如何从 BackStack 恢复现有的 Fragment,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18305945/

相关文章:

c# - Xamarin Android 工具栏如何动态添加项目

javascript - 通过 webview 内的 blob 链接下载 pdf

android - 何时是提交子 fragment 的最佳时间?

android - 如何压缩与 fragment 标签匹配的后台 fragment ?

android - 如何从最近导出的 Activity 中排除?

android - 为 Android 排序 ArrayAdapter

android - 如何为 Android 平板电脑布局实现固定左 Pane ?

java - Android 应用程序在清除 RAM 内存后崩溃

Android - 如何检测堆栈中的最终 Activity

android - 索引 2 处不透明部分的非法字符 flutter