Android 4.2: comportamiento de pila posterior con fragmentos anidados

108

Con Android 4.2, la biblioteca de soporte obtuvo soporte para fragmentos anidados, consulte aquí . He jugado con él y encontré un comportamiento / error interesante con respecto a la pila de actividades y getChildFragmentManager () . Cuando se usa getChildFragmentManager () y addToBackStack (nombre de cadena), al presionar el botón Atrás, el sistema no corre hacia abajo en la pila de actividades al fragmento anterior. Por otro lado, cuando se usa getFragmentManager () y addToBackStack (nombre de cadena), al presionar el botón Atrás, el sistema regresa al fragmento anterior.

Para mí, este comportamiento es inesperado. Al presionar el botón de retroceso en mi dispositivo, espero que aparezca el último fragmento agregado a la pila de actividades, incluso si el fragmento se agregó a la pila de actividades en el administrador de fragmentos de los niños.

¿Es este comportamiento correcto? ¿Es este comportamiento un error? ¿Hay alguna solución 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);
    }
}

}
AZ13
fuente
hmmm eso es muy interesante. No espero ese comportamiento tampoco. Si este es el caso, entonces parece que podríamos haber anulado OnBackPressed y en ese método obtener el fragmento con fragmentos secundarios y luego obtener el ChildFragmentManager para ejecutar una transacción emergente.
Marco RS
@Marco, Exactamente, pero sin embargo es una solución. ¿Por qué la API no funciona correctamente?
Egor

Respuestas:

67

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

Nuevamente, preparé esta solución basada en la respuesta de @Sean anterior.

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

Nota: DesdegetFragments método ahora es un método privado, esta solución no funcionará. Puede consultar los comentarios para ver un enlace que sugiera una solución sobre esta situación.

ismailarilik
fuente
2
林奕 忠 La respuesta debe usarse en lugar de esta: maneja correctamente múltiples fragmentos anidados
Hameno
¿Es realmente un código que funciona? ¿Incluso getFragments no es un método público? stackoverflow.com/a/42572412/4729203
wonsuc
Funcionó una vez, pero es posible que no funcione ahora si getFragments ya no es público.
ismailarilik
1
getFragmentsahora es público.
grrigore
esto no funciona correctamente si tiene FragA -> Frag B -> Frag C dentro de Pager ...
Zhar
57

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

Para una solución alternativa que he usado 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();          
    }
}
Sean
fuente
12
Desafortunadamente, esta no es una solución adecuada y solo una solución para el caso más simple. Suponga que tiene un fragmento, que contiene otro fragmento, que contiene otro fragmento y así sucesivamente. Luego, debe propagar la llamada hasta el último fragmento que contiene otro o más fragmentos: /
AZ13
@MuhammadBabar No, no lo harás. Si es nulo, la segunda parte de la declaración no se evaluará porque ya se determinó que es falsa. el operando es & y no | .
Sean
25

La verdadera respuesta a esta pregunta está en la función de Fragment Transaction llamada setPrimaryNavigationFragment.

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

Debe configurar esta función en el fragmento principal inicial cuando la actividad lo agrega. Tengo una función replaceFragment dentro de mi actividad que se ve así:

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

Esto le da el mismo comportamiento que si volviera a hacer clic desde el Fragmento B normal al Fragmento A, ¡excepto que ahora también está en los fragmentos secundarios!

Mario Bouchedid
fuente
3
¡Gran descubrimiento! Esto resuelve el problema mencionado por el OP de una manera mucho más limpia y menos pirateada.
Jona
Eso se bloquea a menos que forme parte de la biblioteca de navegación como HavHostFragment
Didi
22

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

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();
}
林奕 忠
fuente
no parece funcionar cuando hay varios niveles de fragmentos anidados
splinter123
Nube, ¿me das tu arquitectura de fragmentos anidados? En mi aplicación funciona. Quizás me pierda algo.
林奕 忠
9
El orden para hacer estallar el backstack no es el que esperaba en este ejemplo. Creo que primero debes encontrar el fragmento visible más profundamente anidado en el árbol e intentar hacer estallar el backstack en él, luego avanzar progresivamente hacia arriba en el árbol en busca de otros fragmentos visibles donde puedas hacer pop el backstack hasta obtener uno. Una solución rápida que maneja los casos más simples es cambiar la función onBackPressed (FragmentManager fm) y cambiar el bloque que abre el backstack por el que enumera los fragmentos. De esa manera, primero ocurrirá hacer estallar el backstack en fragmentos de niños anidados más profundamente
Theo
1
Además, cuando tiene muchos fragmentos anidados, cada uno con sus fragmentos secundarios que pueden cubrir parte de la pantalla, la idea de tener una pila auxiliar separada para cada administrador de fragmentos secundarios deja de tener sentido. El backstack debe estar unificado en todos los fragmentos de la actividad y realizar un seguimiento de los cambios de los fragmentos en el orden en que ocurren, independientemente del nivel de anidamiento del fragmento.
Theo
¿Es posible utilizar esta solución sin el SupportFragmentManagery en su lugar solo el Estándar FragmentManager?
Peter Chappy
13

Con esta respuesta, manejará la verificación de retroceso recursiva y le dará a cada fragmento la oportunidad de anular el comportamiento predeterminado. Esto significa que puede hacer que un fragmento que aloje un ViewPager haga algo especial, como desplazarse a la página que se utiliza como back-stack, o desplazarse a la página de inicio y luego, en la siguiente, presione salir.

Agregue esto a su actividad que extiende AppCompatActivity.

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

Agregue esto a su BaseFragment o 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;
}
Simón
fuente
esta es una gran respuesta, trabajando en el nivel de API 23/24 y con soporte lib 24.1.1
Rinav
He estado usando esto durante algún tiempo, es una gran solución, pero recientemente se han compilado advertencias sobre el acceso directo a getFragments () desde un grupo de bibliotecas externo. ¿Ya ha actualizado con una solución alternativa?
Simon Huckett
¿Es algo arreglado o en 2020 todavía tenemos el problema y deberíamos implementar su solución ?? !!
Zhar
3

Este código navegará por el árbol de administradores de fragmentos y devolverá el último que se agregó que tiene fragmentos que pueda sacar 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;
}

Llame al método:

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

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

  super.onBackPressed();
}
AndroidDev
fuente
¡Esto funciona para todos los fragmentos anidados si están registrados correctamente! +100
Pratik Saluja
2

La razón es que su actividad se deriva de FragmentActivity, que maneja la pulsación de la tecla ATRÁS (consulte la línea 173 de FragmentActivity .

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

  • definir una interfaz OnBackKeyPressedListener con un solo método void onBackKeyPressed()
  • implementó esta interfaz en los fragmentos "superiores" que muestra ViewPager
  • anulando onKeyDown y detectando BACK press, y llamando a onBackKeyPressed en un fragmento actualmente activo en el paginador de vista.

También tenga en cuenta que estoy usando getChildFragmentManager()los fragmentos para anidar correctamente los fragmentos. Puede ver una discusión y una explicación en esta publicación de desarrolladores de Android .

miha
fuente
2

si está utilizando un fragmento en un fragmento, utilice getChildFragmentManager()

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

Si está usando un fragmento secundario y lo reemplaza, use getParentFragmentManager()

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

si ambos no funcionan para ti, prueba esto getActivity().getSupportFragmentManager()

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

Pude manejar la pila de fragmentos agregando al fragmento principal 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úrese de que cuando confirme el fragmento que debe tener una pila, debe agregar el método addToBackstack.

EkKoZ
fuente
1

Esta solución puede ser mejor, porque verifica 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 su actividad onBackPressed, simplemente puede 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();
                }
ahmed_khan_89
fuente
1

Gracias a todos por su ayuda, esta (versión modificada) 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: Probablemente también 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 por defecto 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 Fragmento en ese ID de recurso.

Steven L.
fuente
1

Más de 5 años y este tema sigue siendo relevante. Si no desea utilizar fragmentManager.getFragments () debido a su restricción. Amplíe y use 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 de las clases anteriores maneja su propia pila para cada uno de sus hijos, y los hijos de los niños, y así sucesivamente. El backstack es simplemente un registro de etiquetas / ID de "fragmentos activos". Entonces, la advertencia es proporcionar siempre una etiqueta y / o identificación para su fragmento.

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

Ejemplo:

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

Lo he implementado correctamente si alguien no encontró ninguna respuesta hasta ahora

simplemente agregue este método en el fragmento anidado de su hijo

   @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 );
}
Sunil Chaudhary
fuente
0

Resolví este problema manteniendo el fragmento abierto actualmente en propiedad de la actividad. Entonces anulo el método

 // INSIDE ACTIVITY
 override fun onBackPressed() {
    val fragment = currentFragment

    if(fragment != null && fragment.childFragmentManager.fragments.any()){
        fragment.childFragmentManager.popBackStack()
    }
    else {
        super.onBackPressed()
    }
}

Así es como agrego un fragmento secundario al fragmento abierto actualmente desde dentro de sí mismo.

 // INSIDE FRAGMENT
 menu_fragment_view.setBackgroundColor(-((Math.random() * 10000 ).toInt() % 30000)) // to see change
 add_fragment_button.setOnClickListener {
        val transaction = childFragmentManager.beginTransaction()

        transaction.add(R.id.menu_fragment_fragment_holder, MenuFragment())

        transaction.addToBackStack(null)

        transaction.commit()
    }

Este es el diseño xml del fragmento principal y el fragmento agregado

 <?xml version="1.0" encoding="utf-8"?>
 <androidx.constraintlayout.widget.ConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:id="@+id/menu_fragment_view">
     <Button
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintLeft_toLeftOf="parent"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:id="@+id/add_fragment_button"
         android:text="Just add fragment"/>
     <FrameLayout
         android:id="@+id/menu_fragment_fragment_holder"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintLeft_toLeftOf="parent"/> 
</androidx.constraintlayout.widget.ConstraintLayout>
Michal Zhradnk Nono3551
fuente
0

Después de observar algunas de las soluciones presentadas aquí, descubrí que para permitir flexibilidad y control para el fragmento principal en cuanto a cuándo hacer estallar la pila o cuándo debe ignorar la acción de retroceso, prefiero usar dicha implementación:

Definición de una interfaz "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

}

Anulando "onBackPressed" en la actividad principal (o en BaseActivity):

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

Y luego permita que el fragmento principal se maneje como desee, por ejemplo:

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
}

Esto permite evitar pensar que hay algún fragmento secundario legítimo para eliminar cuando se usa un paginador de vista, por ejemplo (y el recuento de entradas de la pila de retroceso> 0).

Hice
fuente
-1

Si tiene un DialogFragmentque a su vez tiene fragmentos anidados, la 'solución alternativa' es un poco diferente. En lugar de establecer un onKeyListeneren el rootView, deberá hacerlo con el Dialog. También estarás configurando un DialogInterface.OnKeyListenery no Viewuno. ¡Por supuesto, recuerda addToBackStack!

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

Esto es lo que debe 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;
    }
Sachin Ahuja
fuente
No es realmente comprensible o no está completo. DialogFragment o su Fragmento de superclase no tienen setOnKeyListener.
Ixx
@Ixx Debe configurar el oyente en Dialog(no DialogFragment) y el oyente que usa es DialogInterface.OnKeyListener: el código está funcionando y completo (y en uso). ¿Por qué no proporciona su situación y quizás yo pueda ayudar?
Sachin Ahuja
-1

para ChildFragments esto funciona ...

@Override
    public void onBackPressed() {

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