studio remove provide popbackstack from activity android android-fragments android-nested-fragment

remove - fragment back navigation android



Android 4.2: comportamiento de back stack con fragmentos anidados (12)

Con esta respuesta manejará la comprobación recursiva y le dará a cada fragmento la oportunidad de anular el comportamiento predeterminado. Esto significa que puede tener un fragmento que aloja un ViewPager para hacer algo especial, como desplazarse a la página que, como una pila de respaldo, o desplazarse a la página de inicio y luego en la siguiente, presionar exit.

Agregue esto a su actividad que extiende AppCompatActivity.

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

Agregue esto a su BaseFragment o a la clase de la que puede heredar todos sus fragmentos.

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

Con Android 4.2, la biblioteca de soporte obtuvo soporte para fragmentos anidados, vea aquí . He jugado con eso y encontré un comportamiento / error interesante con respecto a back stack y getChildFragmentManager() . Al usar getChildFragmentManager () y addToBackStack (String name), al presionar el botón Atrás, el sistema no corre por la pila de respaldo hasta el fragmento anterior. Por otro lado, al usar getFragmentManager() y addToBackStack (String name), al presionar el botón Atrás, el sistema regresa al fragmento anterior.

Para mí, este comportamiento es inesperado. Al presionar el botón Atrás en mi dispositivo, espero que el último fragmento añadido a la pila de respaldo se revele, incluso si el fragmento se agregó a la pila de respaldo en el administrador de fragmentos de los niños.

¿Es este comportamiento correcto? ¿Este comportamiento es un error? ¿Hay una solución alternativa para este problema?

código de muestra con 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); } } }

código de muestra con 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); } } }


El motivo es que su actividad se deriva de FragmentActivity, que maneja la tecla ATRÁS (consulte la línea 173 de FragmentActivity .

En nuestra aplicación, estoy usando una ViewPager (con fragmentos) y cada fragmento puede tener fragmentos anidados. La forma en que he manejado esto es por:

  • definir una interfaz OnBackKeyPressedListener con un único método void onBackKeyPressed()
  • implementó esta interfaz en los fragmentos "superiores" que muestra ViewPager
  • anulando onKeyDown y detectando BACK presiona, y llama a onBackKeyPressed en un fragmento actualmente activo en el buscapersonas.

También tenga en cuenta que estoy usando getChildFragmentManager() en los fragmentos para jerarquizar correctamente los fragmentos. Puedes ver una discusión y una explicación en esta publicación de android-developers .


Esta solución puede ser mejor, porque comprueba todos los niveles de fragmentos anidados:

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

en tu actividad en onBackPressed puedes simplemente llamarlo de esta manera:

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


Esta solución puede ser una mejor versión de la respuesta @msms:

Versión de Fragmento anidado

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


Esta solución puede ser una mejor versión de la respuesta de @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(); }

De nuevo, preparé esta solución en base a la respuesta @Sean anterior.

Como dijo @ AZ13, esta solución solo es posible en situaciones de fragmentos infantiles de un nivel. En el caso de fragmentos de múltiples niveles, las obras se vuelven un poco complejas, por lo que recomiendo que pruebes esta solución solo con el caso factible que he dicho. =)

Nota: Dado que el método getFragments ahora es un método privado, esta solución no funcionará. Puede verificar los comentarios de un enlace que sugiere una solución sobre esta situación.


Este código navegará por el árbol de administradores de fragmentos y devolverá el último que se agregó que tenga fragmentos que pueda extraer de la pila:

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

Llamar al método:

@Override public void onBackPressed() { FragmentManager fm = getLastFragmentManagerWithBack(getSupportFragmentManager()); if (fm.getBackStackEntryCount() > 0) { fm.popBackStack(); return; } super.onBackPressed(); }


Gracias a todos por su ayuda, esta (versión retocada) funciona para mí:

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

NOTA: También es probable que desee establecer el color de fondo de estos fragmentos anidados en el color de fondo de la ventana del tema de la aplicación, ya que de forma predeterminada son transparentes. Algo fuera del alcance de esta pregunta, pero se logra resolviendo el atributo android.R.attr.windowBackground y configurando el fondo de la vista de Fragment a ese ID de recurso.


Más de 5 años y este problema sigue siendo relevante. Si no desea utilizar fragmentManager.getFragments () debido a su restricción. Extiende y usa las siguientes clases:

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

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

Explicación:

Cada actividad y fragmento extendido desde las clases anteriores administra su propia pila de respaldo para cada uno de sus hijos, y los hijos de los niños, y así sucesivamente. El backstack es simplemente un registro de tags / ids de "fragmento activo". Por lo tanto, la advertencia es proporcionar siempre una etiqueta y / o identificación para su fragmento.

Al agregar a la backstack en childFragmentManager, también deberá llamar a "addToParentBackStack ()". Esto asegura que la etiqueta / identificación del fragmento se agrega en el fragmento / actividad principal para pops posteriores.

Ejemplo:

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


Parece un error. Eche un vistazo a: http://code.google.com/p/android/issues/detail?id=40323

Para una solución alternativa que he utilizado con éxito (como se sugiere en los comentarios):

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


Pude manejar el fragmento de la pila posterior añadiendo al fragmento padre este método en el método onCreate View () y pasando la vista raíz.

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

El método isEnableFragmentBackStack () es una bandera booleana para saber cuándo estoy en el fragmento principal o en el siguiente.

Asegúrate de que cuando comprometes el fragmento que necesita tener stack, entonces debes agregar el método addToBack.


Si tiene un DialogFragment que a su vez tiene fragmentos anidados, la ''solución'' es un poco diferente. En lugar de configurar un onKeyListener a la vista rootView , tendrá que hacer eso con el rootView Dialog . También configurará DialogInterface.OnKeyListener y no View . Por supuesto, recuerda addToBackStack !

Por cierto, tener 1 fragmento en el backstack para delegar la devolución de llamada a la actividad es mi caso de uso personal. Los escenarios típicos pueden ser para que el recuento sea 0.

Esto es lo que debes hacer dentro de 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; }


para ChildFragments esto funciona ...

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