Android:按返回键返回上一个片段

19

我已经实现了导航抽屉,它是 Activity 的一个子类。我的应用程序中有很多片段。我的问题在这里:

假设有 3 个片段:

片段1:片段2:片段3

当我启动应用程序时,加载片段1。 当我在片段1上点击某些组件时,我被导航到片段2,依此类推。

所以就像这样:

片段1 > 片段2 > 片段3

当我从片段2按下返回键时,我被导航回到片段1 但是当我从片段3按下返回键时,我被导航回到片段1(而不是片段2)

我希望我的应用程序在按下后退键时像这样

片段1 < 片段2 < 片段3

我使用的 Fragment、FragmentManager 和 FragmentTransaction 如下:

MyFragment fragment = new MyFragment();
FragmentManager fragmentManager = getFragmentManager();

fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).addToBackStack(null)commit();

我尝试在我的MainActivity中重写onBackPressed():

@Override
public void onBackPressed() {


        getFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
        int count = getFragmentManager().getBackStackEntryCount();
        if (count == 0)
               super.onBackPressed();
    }
5个回答

36

将您的Activity#onBackPressed()方法更新为:

@Override
public void onBackPressed() {
    if (getFragmentManager().getBackStackEntryCount() > 0) {
        getFragmentManager().popBackStack();
    } else {
        super.onBackPressed();
    }
}

你的实现不能正常工作的原因是因为方法FragmentManager#popBackStack()是异步的,调用后不会立即发生。

从文档中可以看出:

  

此函数是异步的-它将请求入队以进行弹出操作,但在应用程序返回到其事件循环之前,该操作不会执行。

参考:http://developer.android.com/reference/android/app/FragmentManager.html#popBackStack(java.lang.String,%20int)


你的代码在某些部分是有效的。问题在于: 假设我按照以下方式导航>> Fragment_1> Fragment_2> Fragment_3,然后从导航抽屉中再次选择Fragment_1并按照以下相同的方式导航,最后按下返回键,我发现以下序列Fragment_1 < Fragment_2 < Fragment_3 < Fragment_1 < Fragment_2 < Fragment_3。 - Harsh
您之所以会得到这个序列,是因为您执行的每个 FragmentManager 操作都通过 FragmentTransaction#addToBackStack() 添加到了返回栈中。我再次阅读了您的问题,仍然不清楚您在这种情况下期望得到什么。请澄清一下。 - Leandro
实际上,您的代码解决了我的问题。但是如何更改以下序列:: Fragment_1 < Fragment_2 < Fragment_3 < Fragment_1 < Fragment_2 < Fragment_3 __to__ Fragment_1 < Fragment_2 < Fragment_3 .....无论用户重复操作如何,例如Fragment_1> Fragment_2> Fragment_3。如果您仍然没有理解我的意思,那么我问题的另一个术语是删除相同片段的冗余。 - Harsh
我猜你打算做的是避免在“FragmentManager”和后退栈中出现重复条目,对吧?如果是这样,你可以在提交时设置一个标签来检查Fragment是否已经在栈中,并使用FragmentManager#findFragmentByTag(String TAG)进行搜索,如果它没有返回空值,则不要调用addToBackStack()。如果我的回答解决了你的问题,请将其标记为问题的答案。 - Leandro
你能用一个简短的例子来说明吗?(查找 findFragmentByTag(...) 的代码) - Harsh

3
您需要根据此处所述,实现自己的后退堆栈。
每当您从一个片段导航到另一个片段时,请调用pushFragments()。在片段中单击返回按钮时,请调用popFragments()。
简言之,您需要为每个选项卡实现独立的后退堆栈。
public void onBackPressed()
{
    FragmentManager fm = getActivity().getSupportFragmentManager();
    fm.popBackStack();
}

2

关键在于FragmentManager#executePendingTransactions();

对于嵌套的片段,我也使用这个方法...

/**
 * if there is a fragment and the back stack of this fragment is not empty,
 * then emulate 'onBackPressed' behaviour, because in default, it is not working.
 *
 * @param fm the fragment manager to which we will try to dispatch the back pressed event.
 * @return {@code true} if the onBackPressed event was consumed by a child fragment, otherwise
 */
public static boolean dispatchOnBackPressedToFragments(FragmentManager fm) {

    List<Fragment> fragments = fm.getFragments();
    boolean result;
    if (fragments != null && !fragments.isEmpty()) {
        for (Fragment frag : fragments) {
            if (frag != null && frag.isAdded() && frag.getChildFragmentManager() != null) {
                // go to the next level of child fragments.
                result = dispatchOnBackPressedToFragments(frag.getChildFragmentManager());
                if (result) return true;
            }
        }
    }

    // if the back stack is not empty then we pop the last transaction.
    if (fm.getBackStackEntryCount() > 0) {
        fm.popBackStack();
        fm.executePendingTransactions();
        return true;
    }

    return false;
}

在我的onBackPressed方法中:

                if (!FragmentUtils.dispatchOnBackPressedToFragments(fm)) {
                    // if no child fragment consumed the onBackPressed event,
                    // we execute the default behaviour.
                    super.onBackPressed();
                }

1
非常有用的答案。 - Umang Mathur

0
在您的主活动中,使用此代码来清除堆栈。
int count = getFragmentManager().getBackStackEntryCount();
        if(count>0){
            for (int i = 0; i <count; i++) {
                getFragmentManager().popBackStack();
            }
        }

然后在您的主活动的返回按下时执行以下操作

 int count = getFragmentManager().getBackStackEntryCount();

     if (count == 0) {
         super.onbackpressed();
        }
else {
        getFragmentManager().popBackStack();
    }
 }

0

这是我编写并测试过的可用代码,它会对你有所帮助。

 private static final int TIME_INTERVAL = 2000;
private long mBackPressed;
 private void applyExit() {
    if (mBackPressed + TIME_INTERVAL > System.currentTimeMillis()) {
        finish();
    } else {
         Toast.makeText(this,"Press Again to exit",Toast.LENGTH_LONG).show();
    }
    mBackPressed = System.currentTimeMillis();
}

@Override
public void onBackPressed() {
    fm = getSupportFragmentManager();
    if (drawer.isDrawerOpen(GravityCompat.START)) {
        drawer.closeDrawer(GravityCompat.START);
    }
    if (fm.getFragments().size() <= 1) {
        applyExit();
    } else {
        for (Fragment frag : fm.getFragments()) {
            if (frag == null) {
                applyExit();
                return;
            }
            if (frag.isVisible()) {
                FragmentManager childFm = frag.getChildFragmentManager();
                if (childFm.getFragments() == null) {
                    super.onBackPressed();
                    return;
                }
                if (childFm.getBackStackEntryCount() > 0) {
                    childFm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
                    return;
                } else {
                    fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
                    return;
                }
            }
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接