Android 4.2:使用嵌套的片段返回堆栈行为

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

对我而言,这种行为是意想不到的。 通过按下设备上的后退button,我期望即使片段被添加到孩子的片段pipe理器中的后退堆栈中,最后添加的片段也将被popup。

这种行为是正确的吗? 这种行为是一个错误? 有没有解决这个问题的方法?

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

看起来像一个错误。 看看: http : //code.google.com/p/android/issues/detail?id = 40323

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

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

这个解决scheme可能是更好的@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准备了这个解决scheme。

正如@ AZ13所说,这个解决scheme只能在一个级别的小孩片段情况下使用。 在多级碎片的情况下,工作变得有点复杂,所以我build议试试这个解决scheme只有我说的可行的情况。 =)

注意:由于getFragments方法现在是一个私有方法,因此此解决scheme将不起作用。 你可以检查一个链接的评论,这个链接build议一个关于这个情况的解

这个解决scheme可能是更好的@msms版本回答:

嵌套的片段版本

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

有了这个答案,它将处理recursion后检查,并给每个片段的机会来覆盖默认行为。 这意味着您可以拥有一个承载ViewPager的片段,做一些特殊的事情,例如滚动到作为后备栈的页面,或滚动到主页,然后在下一个后退按下退出。

将此添加到扩展AppCompatActivity的Activity中。

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

把它添加到你的BaseFragment或者你可以让你的所有碎片inheritance的类中。

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

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

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

  • 用一个方法定义一个接口OnBackKeyPressedListener void onBackKeyPressed()
  • 在ViewPager显示的“顶部”片段中实现了这个接口
  • 重写onKeyDown并检测BACK按键,并在视图寻呼机中当前活动的片段中调用onBackKeyPressed。

另外请注意,我使用片段中的getChildFragmentManager()来正确地嵌套片段。 你可以在这个android-developers文章中看到讨论和解释。

我能够通过在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方法。

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

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

感谢大家的帮助,这(调整版本)为我工作:

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

注意:您可能还需要将这些嵌套片段的背景颜色设置为应用程序主题的窗口背景颜色,因为默认情况下它们是透明的。 这个问题的范围之外,但它是通过parsing属性android.R.attr.windowBackground,并将碎片视图的背景设置为该资源ID。

这段代码将浏览片段pipe理器的树,并返回最后一个添加了碎片的碎片,

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

如果您有一个DialogFragment反过来有嵌套的片段,“解决方法”是有点不同。 不要将onKeyListener设置为onKeyListener ,而需要使用Dialog来完成。 你也将设置一个DialogInterface.OnKeyListener而不是View之一。 当然,记得addToBackStack

顺便说一下,在后台有一个片段,用于将callback委托给活动,这是我个人的用例。 典型的情况可能是计数为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; } 

为ChildFragments这个工程..

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