ChatGPT解决这个技术问题 Extra ChatGPT

Android 4.2:带有嵌套片段的回栈行为

在 Android 4.2 中,支持库支持嵌套片段 see here。我玩过它,发现了一个关于回栈和 getChildFragmentManager() 的有趣行为/错误。使用 getChildFragmentManager() 和 addToBackStack(String name) 时,通过按下后退按钮,系统不会将后退堆栈运行到前一个片段。另一方面,当使用 getFragmentManager() 和 addToBackStack(String name) 时,通过按下后退按钮系统返回到前一个片段。

对我来说,这种行为是出乎意料的。通过按下我设备上的后退按钮,我期望最后添加到后退堆栈的片段将被弹出,即使该片段已添加到子片段管理器中的后退堆栈。

这种行为正确吗?这种行为是错误吗?这个问题有解决办法吗?

带有 getChildFragmentManager() 的示例代码:

public class FragmentceptionActivity extends FragmentActivity {

@Override
protected void onCreate(Bundle arg0) {
    super.onCreate(arg0);

    final FrameLayout wrapper1 = new FrameLayout(this);
    wrapper1.setLayoutParams(new FrameLayout.LayoutParams(
            FrameLayout.LayoutParams.MATCH_PARENT,
            FrameLayout.LayoutParams.MATCH_PARENT));
    wrapper1.setId(1);

    final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
            FrameLayout.LayoutParams.MATCH_PARENT,
            FrameLayout.LayoutParams.WRAP_CONTENT);
    params.topMargin = 0;

    final TextView text = new TextView(this);
    text.setLayoutParams(params);
    text.setText("fragment 1");
    wrapper1.addView(text);

    setContentView(wrapper1);

    getSupportFragmentManager().beginTransaction().addToBackStack(null)
            .add(1, new Fragment1()).commit();
}

public class Fragment1 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper2 = new FrameLayout(getActivity());
        wrapper2.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper2.setId(2);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 100;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 2");
        wrapper2.addView(text);

        return wrapper2;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        getFragmentManager().beginTransaction().addToBackStack(null)
                .add(2, new Fragment2()).commit();
    }
}

public class Fragment2 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper3 = new FrameLayout(getActivity());
        wrapper3.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper3.setId(3);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 200;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 3");
        wrapper3.addView(text);

        return wrapper3;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        getChildFragmentManager().beginTransaction().addToBackStack(null)
                .add(3, new Fragment3()).commit();
    }
}

public class Fragment3 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper4 = new FrameLayout(getActivity());
        wrapper4.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper4.setId(4);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 300;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 4");
        wrapper4.addView(text);

        return wrapper4;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        getChildFragmentManager().beginTransaction().addToBackStack(null)
                .add(4, new Fragment4()).commit();
    }
}

public class Fragment4 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper5 = new FrameLayout(getActivity());
        wrapper5.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper5.setId(5);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 400;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 5");
        wrapper5.addView(text);

        return wrapper5;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
    }
}

}

带有 getFragmentManager() 的示例代码:

public class FragmentceptionActivity extends FragmentActivity {

@Override
protected void onCreate(Bundle arg0) {
    super.onCreate(arg0);

    final FrameLayout wrapper1 = new FrameLayout(this);
    wrapper1.setLayoutParams(new FrameLayout.LayoutParams(
            FrameLayout.LayoutParams.MATCH_PARENT,
            FrameLayout.LayoutParams.MATCH_PARENT));
    wrapper1.setId(1);

    final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
            FrameLayout.LayoutParams.MATCH_PARENT,
            FrameLayout.LayoutParams.WRAP_CONTENT);
    params.topMargin = 0;

    final TextView text = new TextView(this);
    text.setLayoutParams(params);
    text.setText("fragment 1");
    wrapper1.addView(text);

    setContentView(wrapper1);

    getSupportFragmentManager().beginTransaction().addToBackStack(null)
            .add(1, new Fragment1()).commit();
}

public class Fragment1 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper2 = new FrameLayout(getActivity());
        wrapper2.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper2.setId(2);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 100;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 2");
        wrapper2.addView(text);

        return wrapper2;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        getFragmentManager().beginTransaction().addToBackStack(null)
                .add(2, new Fragment2()).commit();
    }
}

public class Fragment2 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper3 = new FrameLayout(getActivity());
        wrapper3.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper3.setId(3);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 200;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 3");
        wrapper3.addView(text);

        return wrapper3;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        getFragmentManager().beginTransaction().addToBackStack(null)
                .add(3, new Fragment3()).commit();
    }
}

public class Fragment3 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper4 = new FrameLayout(getActivity());
        wrapper4.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper4.setId(4);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 300;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 4");
        wrapper4.addView(text);

        return wrapper4;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        getFragmentManager().beginTransaction().addToBackStack(null)
                .add(4, new Fragment4()).commit();
    }
}

public class Fragment4 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper5 = new FrameLayout(getActivity());
        wrapper5.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper5.setId(5);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 400;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 5");
        wrapper5.addView(text);

        return wrapper5;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
    }
}

}
嗯,这很有趣。我也不希望这种行为。如果是这种情况,那么似乎我们可能已经覆盖 OnBackPressed 并在该方法中获取带有子片段的片段,然后获取 ChildFragmentManager 以执行弹出事务。
@Marco,没错,但这是一种解决方法。为什么 API 不能正常工作?

i
ismailarilik

此解决方案可能是@Sean 答案的更好版本:

@Override
public void onBackPressed() {
    // 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
    FragmentManager fm = getSupportFragmentManager();
    for (Fragment frag : fm.getFragments()) {
        if (frag.isVisible()) {
            FragmentManager childFm = frag.getChildFragmentManager();
            if (childFm.getBackStackEntryCount() > 0) {
                childFm.popBackStack();
                return;
            }
        }
    }
    super.onBackPressed();
}

同样,我根据上面的@Sean 回答准备了这个解决方案。

正如@AZ13 所说,此解决方案仅在一级子碎片情况下可行。在多级片段的情况下,工作变得有点复杂,所以我建议只在我所说的可行情况下尝试这个解决方案。 =)

注意: 由于 getFragments 方法现在是私有方法,因此此解决方案将不起作用。您可以查看有关此情况的建议解决方案的链接的评论。


应该使用林奕忠的答案而不是这个答案 - 它正确处理多个嵌套片段
它真的是工作代码吗?甚至 getFragments 也不是公共方法? stackoverflow.com/a/42572412/4729203
它曾经工作过,但如果 getFragments 不再公开,现在可能无法工作。
getFragments 现已公开。
如果您在 Pager 中有 FragA -> Frag B -> Frag C,这将无法正常工作...
S
Sean

似乎是一个错误。看看:http://code.google.com/p/android/issues/detail?id=40323

对于我已成功使用的解决方法(如评论中所建议):

    @Override
public void onBackPressed() {

    // If the fragment exists and has some back-stack entry
    if (mActivityDirectFragment != null && mActivityDirectFragment.getChildFragmentManager().getBackStackEntryCount() > 0){
        // Get the fragment fragment manager - and pop the backstack
        mActivityDirectFragment.getChildFragmentManager().popBackStack();
    }
    // Else, nothing in the direct fragment back stack
    else{
        // Let super handle the back press
        super.onBackPressed();          
    }
}

不幸的是,这不是一个合适的解决方案,只是最简单情况的解决方法。假设您有一个片段,其中包含另一个片段,其中包含另一个片段,依此类推。然后你必须将调用传播到包含另一个或多个片段的最后一个片段:/
@MuhammadBabar 不,你不会。如果它为空,则不会评估语句的第二部分,因为它已经被确定为假。操作数是 & 而不是 | .
M
Mario Bouchedid

这个问题的真正答案在于名为 setPrimaryNavigationFragment 的 Fragment Transaction 函数。

/**
 * Set a currently active fragment in this FragmentManager as the primary navigation fragment.
 *
 * <p>The primary navigation fragment's
 * {@link Fragment#getChildFragmentManager() child FragmentManager} will be called first
 * to process delegated navigation actions such as {@link FragmentManager#popBackStack()}
 * if no ID or transaction name is provided to pop to. Navigation operations outside of the
 * fragment system may choose to delegate those actions to the primary navigation fragment
 * as returned by {@link FragmentManager#getPrimaryNavigationFragment()}.</p>
 *
 * <p>The fragment provided must currently be added to the FragmentManager to be set as
 * a primary navigation fragment, or previously added as part of this transaction.</p>
 *
 * @param fragment the fragment to set as the primary navigation fragment
 * @return the same FragmentTransaction instance
 */
public abstract FragmentTransaction setPrimaryNavigationFragment(Fragment fragment);

当活动添加它时,您必须在初始父片段上设置此功能。我的活动中有一个 replaceFragment 函数,如下所示:

public void replaceFragment(int containerId, BaseFragment fragment, boolean addToBackstack) {
    FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.setPrimaryNavigationFragment(fragment);
    if (addToBackstack) {
        fragmentTransaction.addToBackStack(fragment.TAG);
    }

    fragmentTransaction.replace(containerId, fragment).commit();
}

这为您提供了与从常规片段 B 返回片段 A 相同的行为,除了现在它也在子片段上!


很棒的发现!这以一种更简洁、更简洁的方式解决了 OP 提到的问题。
除非导航库的一部分作为 HavHostFragment 否则会崩溃
i
ismailarilik

此解决方案可能是@ismailarilik 答案的更好版本:

嵌套片段版本

private boolean onBackPressed(FragmentManager fm) {
    if (fm != null) {
        if (fm.getBackStackEntryCount() > 0) {
            fm.popBackStack();
            return true;
        }

        List<Fragment> fragList = fm.getFragments();
        if (fragList != null && fragList.size() > 0) {
            for (Fragment frag : fragList) {
                if (frag == null) {
                    continue;
                }
                if (frag.isVisible()) {
                    if (onBackPressed(frag.getChildFragmentManager())) {
                        return true;
                    }
                }
            }
        }
    }
    return false;
}

@Override
public void onBackPressed() {
    FragmentManager fm = getSupportFragmentManager();
    if (onBackPressed(fm)) {
        return;
    }
    super.onBackPressed();
}

当有多个级别的嵌套片段时,它似乎不起作用
Cloud 你给我你的嵌套片段架构?在我的应用程序中它有效。也许我错过了什么。
在这个例子中,弹出 backstack 的顺序与我预期的不同。我相信您应该首先在树中找到嵌套最深的可见片段并尝试在其上弹出 backstack,然后逐步向上移动树寻找其他可见片段,您可以在其中弹出 backstack 直到您得到一个。处理最简单情况的快速修复方法是更改 onBackPressed(FragmentManager fm) 函数并将弹出回栈的块切换为枚举片段的块。这样,将首先在嵌套更深的子片段中弹出 backstack
此外,当您有很多嵌套片段时,每个片段的子片段可能会覆盖屏幕的一部分,为每个子片段管理器设置单独的后台堆栈的想法就没有意义了。 backstack 应该在 Activity 中的所有 Fragment 中统一,并按照 Fragment 发生的顺序跟踪 Fragment 更改,而不管 Fragment 的嵌套级别如何。
是否可以在没有 SupportFragmentManager 而只使用标准 FragmentManager 的情况下使用此解决方案?
S
Simon

有了这个答案,它将处理递归回溯检查,并让每个片段有机会覆盖默认行为。这意味着您可以让一个承载 ViewPager 的片段执行一些特殊的操作,例如滚动到作为后退堆栈的页面,或者滚动到主页,然后在下一次后退时退出。

将此添加到扩展 AppCompatActivity 的 Activity。

@Override
public void onBackPressed()
{
    if(!BaseFragment.handleBackPressed(getSupportFragmentManager())){
        super.onBackPressed();
    }
}

将此添加到您的 BaseFragment 或您可以继承所有片段的类。

public static boolean handleBackPressed(FragmentManager fm)
{
    if(fm.getFragments() != null){
        for(Fragment frag : fm.getFragments()){
            if(frag != null && frag.isVisible() && frag instanceof BaseFragment){
                if(((BaseFragment)frag).onBackPressed()){
                    return true;
                }
            }
        }
    }
    return false;
}

protected boolean onBackPressed()
{
    FragmentManager fm = getChildFragmentManager();
    if(handleBackPressed(fm)){
        return true;
    }
    else if(getUserVisibleHint() && fm.getBackStackEntryCount() > 0){
        fm.popBackStack();
        return true;
    }
    return false;
}

这是一个很好的答案,在 API 级别 23/24 上工作并支持 lib 24.1.1
现在已经使用了一段时间,这是一个很好的解决方案 - 但最近有关于从外部库组直接访问 getFragments() 的编译警告。您是否已经更新了解决方法?
是否已修复或在 2020 年我们仍然存在问题并且应该实施您的解决方案??!!
J
Johann

此代码将导航片段管理器树并返回最后一个添加的具有任何可以从堆栈弹出的片段的片段:

private FragmentManager getLastFragmentManagerWithBack(FragmentManager fm)
{
  FragmentManager fmLast = fm;

  List<Fragment> fragments = fm.getFragments();

  for (Fragment f : fragments)
  {
    if ((f.getChildFragmentManager() != null) && (f.getChildFragmentManager().getBackStackEntryCount() > 0))
    {
      fmLast = f.getFragmentManager();
      FragmentManager fmChild = getLastFragmentManagerWithBack(f.getChildFragmentManager());

      if (fmChild != fmLast)
        fmLast = fmChild;
    }
  }

  return fmLast;
}

调用方法:

@Override
public void onBackPressed()
{
  FragmentManager fm = getLastFragmentManagerWithBack(getSupportFragmentManager());

  if (fm.getBackStackEntryCount() > 0)
  {
    fm.popBackStack();
    return;
  }

  super.onBackPressed();
}

如果正确注册,这适用于所有嵌套片段! +100
它有效!在测试了其他方法之后,它适用于复杂的嵌套片段。当您在 Main 片段中有一个片段并且该主要片段在 MainActivity 中时,这种方法适合您。
S
Sunil Chaudhary

如果到目前为止有人没有找到任何答案,我已经正确实施了

只需在您的子嵌套片段上添加此方法

   @Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    // This callback will only be called when MyFragment is at least Started.
    OnBackPressedCallback callback = new OnBackPressedCallback(true ) {
        @Override
        public void handleOnBackPressed() {
            // Handle the back button event
            FragmentManager fm= getFragmentManager();
            if (fm != null) {
                if (fm.getBackStackEntryCount() > 0) {
                    fm.popBackStack();
                    Log.e( "Frag","back" );

                }

                List<Fragment> fragList = fm.getFragments();
                if (fragList != null && fragList.size() > 0) {
                    for (Fragment frag : fragList) {
                        if (frag == null) {
                            continue;
                        }
                        if (frag.isVisible()) {
                          Log.e( "Frag","Visible" );
                        }
                    }
                }
            }


        }
    };
    requireActivity().getOnBackPressedDispatcher().addCallback(this, callback);
    super.onCreate( savedInstanceState );
}

感谢此行requireActivity().getOnBackPressedDispatcher().addCallback(this, callback)
m
miha

原因是您的 Activity 派生自 FragmentActivity,它处理 BACK 键按下(参见 FragmentActivity 的第 173 行。

在我们的应用程序中,我使用的是 ViewPager(带有片段),每个片段都可以有嵌套的片段。我处理这个的方式是:

使用单个方法定义接口 OnBackKeyPressedListener void onBackKeyPressed()

在 ViewPager 显示的“顶部”片段中实现了这个接口

覆盖 onKeyDown 并检测 BACK 按下,并在视图寻呼机的当前活动片段中调用 onBackKeyPressed。

另请注意,我在片段中使用 getChildFragmentManager() 来正确嵌套片段。您可以在 this android-developers post 中看到讨论和解释。


T
THCBin

如果您在片段中使用片段,则使用 getChildFragmentManager()

getChildFragmentManager().beginTransaction().replace(R.id.fragment_name, fragment).addToBackStack(null).commit();

如果您正在使用子片段并替换它,请使用 getParentFragmentManager()

getParentFragmentManager().beginTransaction().replace(R.id.fragment_name, fragment).addToBackStack(null).commit();

如果两者都不适合你试试这个getActivity().getSupportFragmentManager()

getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.fragment_name, fragment).addToBackStack(null).commit();

g
geekkoz

我能够通过在 onCreate View() 方法中将此方法添加到父片段并传递根视图来处理片段回栈。

private void catchBackEvent(View v){
    v.setFocusableInTouchMode(true);
    v.requestFocus();
    v.setOnKeyListener( new OnKeyListener()
    {
        @Override
        public boolean onKey( View v, int keyCode, KeyEvent event )
        {
            if( keyCode == KeyEvent.KEYCODE_BACK )
            {
                if(isEnableFragmentBackStack()){
                    getChildFragmentManager().popBackStack();
                                    setEnableFragmentBackStack(false);
                    return true;
                }
                else
                    return false;   
            }
            return false;
        }
    } );
}

isEnableFragmentBackStack() 方法是一个布尔标志,用于知道我何时在主片段或下一个片段上。

确保在提交需要有堆栈的片段时,必须添加 addToBackstack 方法。


a
ahmed_khan_89

这个解决方案可能更好,因为它检查嵌套片段的所有级别:

 /**
 * This method will go check all the back stacks of the added fragments and their nested fragments
 * to the the {@code FragmentManager} passed as parameter.
 * 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 {@code false}.
 */
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 中,您可以简单地这样称呼它:

FragmentManager fm = getSupportFragmentManager();
                // 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
                if (!dispatchOnBackPressedToFragments(fm)) {
                    // if no child fragment consumed the onBackPressed event,
                    // we execute the default behaviour.
                    super.onBackPressed();
                }

S
Steven L

感谢大家的帮助,这个(调整后的版本)对我有用:

@Override
public void onBackPressed() {
    if (!recursivePopBackStack(getSupportFragmentManager())) {
        super.onBackPressed();
    }
}

/**
 * Recursively look through nested fragments for a backstack entry to pop
 * @return: true if a pop was performed
 */
public static boolean recursivePopBackStack(FragmentManager fragmentManager) {
    if (fragmentManager.getFragments() != null) {
        for (Fragment fragment : fragmentManager.getFragments()) {
            if (fragment != null && fragment.isVisible()) {
                boolean popped = recursivePopBackStack(fragment.getChildFragmentManager());
                if (popped) {
                    return true;
                }
            }
        }
    }

    if (fragmentManager.getBackStackEntryCount() > 0) {
        fragmentManager.popBackStack();
        return true;
    }

    return false;
}

注意:您可能还希望将这些嵌套片段的背景颜色设置为应用程序主题的窗口背景颜色,因为默认情况下它们是透明的。有点超出了这个问题的范围,但它是通过解析属性 android.R.attr.windowBackground 并将 Fragment 视图的背景设置为该资源 ID 来完成的。


c
chaser

5年多了,这个问题仍然相关。如果您不想使用 fragmentManager.getFragments() 由于其限制。扩展和使用以下类:

NestedFragmentActivity.java

abstract public class NestedFragmentActivity extends AppCompatActivity {

    private final Stack<Integer> mActiveFragmentIdStack = new Stack<>();
    private final Stack<String> mActiveFragmentTagStack = new Stack<>();

    @Override
    public void onBackPressed() {
        if (mActiveFragmentIdStack.size() > 0 && mActiveFragmentTagStack.size() > 0) {

            // Find by id
            int lastFragmentId = mActiveFragmentIdStack.lastElement();
            NestedFragment nestedFragment = (NestedFragment) getSupportFragmentManager().findFragmentById(lastFragmentId);

            // If cannot find by id, find by tag
            if (nestedFragment == null) {
                String lastFragmentTag = mActiveFragmentTagStack.lastElement();
                nestedFragment = (NestedFragment) getSupportFragmentManager().findFragmentByTag(lastFragmentTag);
            }

            if (nestedFragment != null) {
                nestedFragment.onBackPressed();
            }

            // If cannot find by tag, then simply pop
            mActiveFragmentTagStack.pop();
            mActiveFragmentIdStack.pop();

        } else {
            super.onBackPressed();
        }
    }

    public void addToBackStack(int fragmentId, String fragmentTag) {
        mActiveFragmentIdStack.add(fragmentId);
        mActiveFragmentTagStack.add(fragmentTag);
    }
}

嵌套片段.java

abstract public class NestedFragment extends Fragment {

    private final Stack<Integer> mActiveFragmentIdStack = new Stack<>();
    private final Stack<String> mActiveFragmentTagStack = new Stack<>();

    private NestedFragmentActivity mParentActivity;
    private NestedFragment mParentFragment;

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);

        if (getParentFragment() == null) {
            try {
                mParentActivity = (NestedFragmentActivity) context;
            } catch (ClassCastException e) {
                throw new ClassCastException(context.toString()
                        + " must implement " + NestedFragmentActivity.class.getName());
            }
        } else {
            try {
                mParentFragment = (NestedFragment) getParentFragment();
            } catch (ClassCastException e) {
                throw new ClassCastException(getParentFragment().getClass().toString()
                        + " must implement " + NestedFragment.class.getName());
            }
        }
    }

    public void onBackPressed() {

        if (mActiveFragmentIdStack.size() > 0 && mActiveFragmentTagStack.size() > 0) {

            // Find by id
            int lastFragmentId = mActiveFragmentIdStack.lastElement();
            NestedFragment nestedFragment = (NestedFragment) getChildFragmentManager().findFragmentById(lastFragmentId);

            // If cannot find by id, find by tag
            if (nestedFragment == null) {
                String lastFragmentTag = mActiveFragmentTagStack.lastElement();
                nestedFragment = (NestedFragment) getChildFragmentManager().findFragmentByTag(lastFragmentTag);
            }

            if (nestedFragment != null) {
                nestedFragment.onBackPressed();
            }

            // If cannot find by tag, then simply pop
            mActiveFragmentIdStack.pop();
            mActiveFragmentTagStack.pop();

        } else {
            getChildFragmentManager().popBackStack();
        }
    }

    private void addToBackStack(int fragmentId, String fragmentTag) {
        mActiveFragmentIdStack.add(fragmentId);
        mActiveFragmentTagStack.add(fragmentTag);
    }

    public void addToParentBackStack() {
        if (mParentFragment != null) {
            mParentFragment.addToBackStack(getId(), getTag());
        } else if (mParentActivity != null) {
            mParentActivity.addToBackStack(getId(), getTag());
        }
    }
}

解释:

从上述类扩展的每个活动和片段都为他们的每个孩子和孩子的孩子管理自己的后台堆栈,等等。后台堆栈只是“活动片段”标签/ID 的记录。因此需要注意的是始终为您的片段提供标签和/或 ID。

在 childFragmentManager 中添加到 backstack 时,您还需要调用“addToParentBackStack()”。这确保片段的标签/id 被添加到父片段/活动中以供以后弹出。

例子:

    getChildFragmentManager().beginTransaction().replace(
            R.id.fragment,
            fragment,
            fragment.getTag()
    ).addToBackStack(null).commit();
    addToParentBackStack();

M
Michal Zhradnk Nono3551

我通过将当前打开的片段保留在活动属性中来解决了这个问题。然后我覆盖方法

// 内部活动覆盖乐趣 onBackPressed() { val fragment = currentFragment if(fragment != null && fragment.childFragmentManager.fragments.any()){ fragment.childFragmentManager.popBackStack() } else { super.onBackPressed() } }

这就是我从自身内部将子片段添加到当前打开的片段中的方式。

// INSIDE FRAGMENT menu_fragment_view.setBackgroundColor(-((Math.random() * 10000 ).toInt() % 30000)) // 看变化 add_fragment_button.setOnClickListener { val transaction = childFragmentManager.beginTransaction() transaction.add(R. id.menu_fragment_fragment_holder, MenuFragment()) transaction.addToBackStack(null) transaction.commit() }

这是父片段和添加片段的xml布局

D
Didi

在观察了这里提出的一些解决方案之后,我发现为了让父片段在弹出堆栈或何时应该忽略返回操作时具有灵活性和控制,我宁愿使用这样的实现:

定义一个“ParentFragment”接口:

interface ParentFragment {
/**
 * Fragments that host child fragments and want to control their BackStack behaviour when the back button is pressed should implement this
 *
 * @return true if back press was handled, false otherwise
 */
fun onBackPressed(): Boolean

}

覆盖父活动(或BaseActivity)中的“onBackPressed”:

override fun onBackPressed() {
    val fm: FragmentManager = supportFragmentManager
    for (frag in fm.fragments) {
        if (frag.isVisible && frag is ParentFragment && frag.onBackPressed()) {
            return
        }
    }
    super.onBackPressed()
}

然后让父片段随心所欲地处理,例如:

override fun onBackPressed(): Boolean {
    val childFragment = childFragmentManager.findFragmentByTag(SomeChildFragment::class.java.simpleName)
    if (childFragment != null && childFragment.isVisible) {
        // Only for that case, pop the BackStack (perhaps when other child fragments are visible don't)
        childFragmentManager.popBackStack()
        return true
    }
    return false
}

这可以避免在使用视图寻呼机时认为有一些合法的子片段要删除(并且返回堆栈条目计数> 0)。


S
Shannon D

这是我的解决方案。我有一个带有子片段的活动。然后,该子片段可以递归地在其 childFragmentManager 上创建自身的更多版本。所以这是我的活动的 onBackPressed 函数的代码。

override fun onBackPressed() {
    // Get the child fragment manager of the displayed fragment
    var childFragmentManager = supportFragmentManager.fragments.first().childFragmentManager

    // If there are nested fragments, handle those first
    if (childFragmentManager.backStackEntryCount > 0) {
        // If there are more nested fragments, go deeper into those
        var nestedChildFragmentManager = childFragmentManager.fragments.first().childFragmentManager
        while (nestedChildFragmentManager.backStackEntryCount > 0) {
            childFragmentManager = nestedChildFragmentManager
            nestedChildFragmentManager = childFragmentManager.fragments.first().childFragmentManager
        }

        // Dismiss the innermost nested fragment
        childFragmentManager.popBackStack()
    } else if (supportFragmentManager.backStackEntryCount > 0) {
        // If there are no nested fragments, dismiss the original fragment
        supportFragmentManager.popBackStack()
    } else {
        // Otherwise, handle the back press as normal
        super.onBackPressed()
    }
}

S
Sachin Ahuja

如果您有一个 DialogFragment 又具有嵌套片段,则“解决方法”会有所不同。无需将 onKeyListener 设置为 rootView,您需要使用 Dialog 来执行此操作。此外,您将设置 DialogInterface.OnKeyListener 而不是 View。当然,请记住addToBackStack

顺便说一句,在后台有 1 个片段用于将回调委托给活动是我的个人用例。典型情况可能是计数为 0。

这是您必须在 onCreateDialog 中执行的操作

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        Dialog dialog =  super.onCreateDialog(savedInstanceState);
        dialog.setOnKeyListener(new DialogInterface.OnKeyListener() {
            @Override
            public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
                if(keyCode == KeyEvent.KEYCODE_BACK){
                    FragmentManager cfm = getChildFragmentManager();
                    if(cfm.getBackStackEntryCount()>1){
                        cfm.popBackStack();
                        return true;
                    }   
                }   
                return false;
            }
        });
        return dialog;
    }

不是很容易理解或不完整。 DialogFragment 或其超类 Fragment 没有 setOnKeyListener。
@Ixx 您必须在 Dialog(而不是 DialogFragment)上设置侦听器,并且您使用的侦听器是 DialogInterface.OnKeyListener - 代码正在运行且完整(并且正在使用中)。你为什么不提供你的情况,也许我可以提供帮助。
u
user3854496

对于 ChildFragments 这有效..

@Override
    public void onBackPressed() {

 if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
            getSupportFragmentManager().popBackStack();
        } else {
            doExit(); //super.onBackPressed();
        }
}

关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅