如果存在,如何从BackStack恢复Fragment

Kai*_*dul 140 android android-fragments back-stack

我正在学习如何使用片段.我有三个实例Fragment在类的顶部初始化.我将片段添加到这样的活动:

声明和初始化:

Fragment A = new AFragment();
Fragment B = new BFragment();
Fragment C = new CFragment();
Run Code Online (Sandbox Code Playgroud)

更换/添加:

FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, A);
ft.addToBackStack(null);
ft.commit();
Run Code Online (Sandbox Code Playgroud)

这些片段工作正常.每个片段都附加到活动,并保存到后台堆栈没有任何问题.

所以当我启动AC,然后B,堆栈看起来像这样:

| |
|B|
|C|
|A|
___
Run Code Online (Sandbox Code Playgroud)

当我按下"后退"按钮时,会B被摧毁并C恢复.

但是,当我A第二次启动片段时,它不会从后端堆栈中恢复,而是添加到后端堆栈的顶部

| |
|A|
|C|
|A|
___
Run Code Online (Sandbox Code Playgroud)

但我想恢复A并销毁它上面的所有碎片(如果有的话).实际上,我只是喜欢默认的后台堆栈行为.

我该如何做到这一点?

预期:( A应该恢复,顶部碎片应该销毁)

| |
| |
| |
|A|
___
Run Code Online (Sandbox Code Playgroud)

编辑:(由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);
    }
Run Code Online (Sandbox Code Playgroud)

问题是,当我启动A,然后B,然后按"后退",B移除并A重新开始.然后第二次按'后退'应退出应用程序.但是它显示一个空白窗口,我必须再次按回来关闭它.

此外,当我启动A,然后B,然后C,然后B再...

预期:

| |
| |
|B|
|A|
___
Run Code Online (Sandbox Code Playgroud)

实际:

| |
|B|
|B|
|A|
___
Run Code Online (Sandbox Code Playgroud)

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

A--*_*--C 266

阅读文档,有一种方法可以根据事务名称或commit提供的id弹出后台堆栈.使用该名称可能更容易,因为它不需要跟踪可能改变的数字并加强"唯一的后栈条目"逻辑.

由于每个只需要一个后向堆栈条目Fragment,因此将后面的状态名称设置为Fragment的类名称(via getClass().getName()).然后在替换a时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();
  }
}
Run Code Online (Sandbox Code Playgroud)

编辑

问题是 - 当我启动A然后启动B,然后按回按钮,B被移除并且A被恢复.并再次按下后退按钮应该退出应用程序.但它显示一个空白窗口,需要另一个按下来关闭它.

这是因为FragmentTransaction它被添加到后栈以确保我们可以稍后将片段弹出.onBackPressed()如果后端堆栈仅包含1,则快速解决此问题是覆盖并完成活动Fragment

@Override
public void onBackPressed(){
  if (getSupportFragmentManager().getBackStackEntryCount() == 1){
    finish();
  }
  else {
    super.onBackPressed();
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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();
  } 
}
Run Code Online (Sandbox Code Playgroud)

条件有点改变,因为选择相同的片段,而它可见也导致重复条目.

执行:

我强烈建议不要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();
Run Code Online (Sandbox Code Playgroud)

所以很简单

replaceFragment (fragmentName);
Run Code Online (Sandbox Code Playgroud)

编辑#2

要在后台堆栈更改时更新抽屉,请创建一个在片段中接受的方法并比较类名称.如果匹配,请更改标题和选择.OnBackStackChangedListener如果有一个有效的片段,还要添加一个并让它调用您的更新方法.

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

getSupportFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener() {

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

  }
});
Run Code Online (Sandbox Code Playgroud)

另一种方法:

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
  }
}
Run Code Online (Sandbox Code Playgroud)

现在,每当后台堆栈发生变化时,标题和检查位置将反映可见Fragment.

  • @RishabhSrivastava记住,"pop"通常会破坏最顶层的片段.至于首先调用哪种方法,它取决于 - 看一下片段生命周期.对于你的情况,我会使用一个`OnBackstackChangedListener`,以便你可以保证你正在处理正确的片段. (2认同)
  • @RishabhSrivastava这也是我建议使用`OnBackstackChangedListener`的原因. (2认同)
  • 您的解决方案听起来不错,但是如果您有三个片段,然后单击它们“ ABCB”,然后再按一次,您将回到“ A”而不是“ C”。这是因为poppopStackImmediate不仅会弹出给定的标签,还会弹出上面的所有内容。周围有工作吗? (2认同)

erl*_*man 8

我认为这种方法可以解决你的问题:

public static void attachFragment ( int fragmentHolderLayoutId, Fragment fragment, Context context, String tag ) {


    FragmentManager manager = ( (AppCompatActivity) context ).getSupportFragmentManager ();
    FragmentTransaction ft = manager.beginTransaction ();

    if (manager.findFragmentByTag ( tag ) == null) { // No fragment in backStack with same tag..
        ft.add ( fragmentHolderLayoutId, fragment, tag );
        ft.addToBackStack ( tag );
        ft.commit ();
    }
    else {
        ft.show ( manager.findFragmentByTag ( tag ) ).commit ();
    }
}
Run Code Online (Sandbox Code Playgroud)

最初发布在 本期杂志中