Fragmentos en el currículum de la pila de actividades

94

Estoy usando el paquete de compatibilidad para usar Fragments con Android 2.2. Al usar fragmentos y agregar transiciones entre ellos al backstack, me gustaría lograr el mismo comportamiento de onResume de una actividad, es decir, cada vez que un fragmento se coloca en "primer plano" (visible para el usuario) después de salir del backstack, me gustaría que se activara algún tipo de devolución de llamada dentro del fragmento (para realizar ciertos cambios en un recurso de IU compartido, por ejemplo).

Vi que no hay devolución de llamada incorporada dentro del marco de fragmentos. ¿Existe una buena práctica para lograrlo?

oriharel
fuente
13
Gran pregunta. Estoy tratando de cambiar el título de la barra de acciones dependiendo de qué fragmento es visible como un caso de uso para este escenario y me parece que falta una devolución de llamada en la API.
Manfred Moser
3
Su actividad puede establecer el título dado que sabe qué fragmentos se muestran en cualquier momento. También supongo que podrías hacer algo en lo onCreateViewque se te pida el fragmento después de un pop.
PJL
1
@PJL +1 Según la referencia esa es la forma en que se debe hacer. Sin embargo, prefiero la forma del oyente
momo

Respuestas:

115

A falta de una mejor solución, esto me funcionó: suponga que tengo 1 actividad (MyActivity) y algunos fragmentos que se reemplazan entre sí (solo uno es visible a la vez).

En MyActivity, agregue este oyente:

getSupportFragmentManager().addOnBackStackChangedListener(getListener());

(Como puede ver, estoy usando el paquete de compatibilidad).

Implementación de getListener:

private OnBackStackChangedListener getListener()
    {
        OnBackStackChangedListener result = new OnBackStackChangedListener()
        {
            public void onBackStackChanged() 
            {                   
                FragmentManager manager = getSupportFragmentManager();

                if (manager != null)
                {
                    MyFragment currFrag = (MyFragment) manager.findFragmentById(R.id.fragmentItem);

                    currFrag.onFragmentResume();
                }                   
            }
        };

        return result;
    }

MyFragment.onFragmentResume()se llamará después de pulsar "Atrás". sin embargo, algunas advertencias:

  1. Se asume que agregó todas las transacciones al backstack (usando FragmentTransaction.addToBackStack())
  2. Se activará con cada cambio de pila (puede almacenar otras cosas en la pila de actividades, como la animación), por lo que podría recibir varias llamadas para la misma instancia de fragmento.
oriharel
fuente
3
Debe aceptar su propia respuesta como correcta si le funcionó.
powerj1984
7
¿Cómo funciona eso en términos de la identificación del fragmento que está consultando? ¿En qué se diferencia cada fragmento y el correcto se encuentra en la pila de actividades?
Manfred Moser
2
@Warpzit, ese método no se llama, y ​​los documentos de Android admiten silenciosamente que nunca se llamará (solo se llama cuando se reanuda la actividad, que nunca, si esa actividad ya está activa)
Adam
2
¡Es ridículo que tengamos que hacer esto! Pero me alegro de que alguien más haya podido encontrar esta "función"
stevebot
3
NO se llama a onResume ()
Marty Miller
33

He cambiado un poco la solución sugerida. Funciona mejor para mí así:

private OnBackStackChangedListener getListener() {
    OnBackStackChangedListener result = new OnBackStackChangedListener() {
        public void onBackStackChanged() {
            FragmentManager manager = getSupportFragmentManager();
            if (manager != null) {
                int backStackEntryCount = manager.getBackStackEntryCount();
                if (backStackEntryCount == 0) {
                    finish();
                }
                Fragment fragment = manager.getFragments()
                                           .get(backStackEntryCount - 1);
                fragment.onResume();
            }
        }
    };
    return result;
}
Brotoo25
fuente
1
explique la diferencia y por qué usar esto.
Enfriador de matemáticas
2
La diferencia entre mi solución y la anterior es que en cada cambio de fragmento como agregar, reemplazar o retroceder en la pila de fragmentos, se llamará al método "onResume" del fragmento superior. No hay ningún ID de fragmento codificado en este método. Básicamente, llama a onResume () en el fragmento que está viendo en cada cambio, sin importar si ya estaba cargado en la memoria o no.
Brotoo25
9
No hay registro para un método llamado getFragments()en SupportFragmentManager ... -1: - /
Protostome
1
Se llama OnResume (). Pero me aparece la pantalla en blanco. ¿Alguna otra forma de resolver este problema?
kavie
Creo que esto conduce a una excepción ArrayOutOfBounds si backStackEntryCount es 0. ¿Me equivoco? Intenté editar la publicación pero fue rechazada.
Mike T
6

Después de una popStackBack(), puede usar la siguiente devolución de llamada: onHiddenChanged(boolean hidden)dentro de su fragmento

usuario2230304
fuente
2
Esto no parece funcionar con fragmentos de compatibilidad de aplicaciones.
Lo-Tan
Eso es lo que usé. ¡Gracias!
Albert Vila Calvo
¡La solución más sencilla! Simplemente agregue una marca si el fragmento está visible actualmente y listo, puede configurar el título de la barra de aplicaciones.
EricH206
4

La siguiente sección en Desarrolladores de Android describe un mecanismo de comunicación que crea devoluciones de llamada de eventos a la actividad . Para citar una línea de él:

Una buena forma de hacerlo es definir una interfaz de devolución de llamada dentro del fragmento y requerir que la actividad del host la implemente. Cuando la actividad recibe una devolución de llamada a través de la interfaz, puede compartir la información con otros fragmentos en el diseño según sea necesario.

Editar: el fragmento tiene un nombre onStart(...)que se invoca cuando el fragmento es visible para el usuario. De manera similar, onResume(...)cuando está visible y funcionando activamente. Estos están vinculados a sus contrapartes de actividad. En resumen: useonResume()

PJL
fuente
1
Solo estoy buscando un método en la clase Fragment que se active cada vez que se le muestre al usuario. ya sea por un FragmentTransaction.add () / replace (), o un FragmentManager.popBackStack ().
oriharel
@oriharel Ver respuesta actualizada. Cuando se carga la actividad, recibirás varias llamadas, onAttach, onCreate, onActivityCreated, onStart, onResume. Si ha llamado a `setRetainInstance (true) ', no obtendrá un onCreate cuando se vuelva a mostrar el fragmento al hacer clic en Atrás.
PJL
15
onStart () nunca se llama al hacer clic en "Atrás" entre los fragmentos de la misma actividad. Probé la mayoría de las devoluciones de llamada en la documentación y ninguna de ellas se llama en tal escenario. vea mi respuesta para una solución.
oriharel
hmm con compat lib v4 Veo un onResume para mi fragmento. Sin embargo, me gusta la respuesta de @ oriharel, ya que distingue entre volverse visible a través de un popBackStack en lugar de simplemente ser llevado al primer plano.
PJL
3

En mi actividad onCreate ()

getSupportFragmentManager().addOnBackStackChangedListener(getListener());

Utilice este método para capturar un fragmento específico y llamar a onResume ()

private FragmentManager.OnBackStackChangedListener getListener()
    {
        FragmentManager.OnBackStackChangedListener result = new FragmentManager.OnBackStackChangedListener()
        {
            public void onBackStackChanged()
            {
                Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
                if (currentFragment instanceof YOURFRAGMENT) {
                    currentFragment.onResume();
                }
            }
        };

        return result;
    }
Quan Nguyen
fuente
2

Un poco mejorado y envuelto en una solución de administrador.

Cosas a tener en cuenta. FragmentManager no es un singleton, solo administra Fragmentos dentro de Activity, por lo que en cada actividad será nuevo. Además, esta solución hasta ahora no tiene en cuenta a ViewPager que llama al método setUserVisibleHint () que ayuda a controlar la visibilidad de los Fragmentos.

Siéntase libre de utilizar las siguientes clases al tratar este problema (usa la inyección Dagger2). Actividad de llamada:

//inject FragmentBackstackStateManager instance to myFragmentBackstackStateManager
FragmentManager fragmentManager = getSupportFragmentManager(); 
myFragmentBackstackStateManager.apply(fragmentManager);

FragmentBackstackStateManager.java:

@Singleton
public class FragmentBackstackStateManager {

    private FragmentManager fragmentManager;

    @Inject
    public FragmentBackstackStateManager() {
    }

    private BackstackCallback backstackCallbackImpl = new BackstackCallback() {
        @Override
        public void onFragmentPushed(Fragment parentFragment) {
            parentFragment.onPause();
        }

        @Override
        public void onFragmentPopped(Fragment parentFragment) {
            parentFragment.onResume();
        }
    };

    public FragmentBackstackChangeListenerImpl getListener() {
        return new FragmentBackstackChangeListenerImpl(fragmentManager, backstackCallbackImpl);
    }

    public void apply(FragmentManager fragmentManager) {
        this.fragmentManager = fragmentManager;
        fragmentManager.addOnBackStackChangedListener(getListener());
    }
}

FragmentBackstackChangeListenerImpl.java:

public class FragmentBackstackChangeListenerImpl implements FragmentManager.OnBackStackChangedListener {

    private int lastBackStackEntryCount = 0;
    private final FragmentManager fragmentManager;
    private final BackstackCallback backstackChangeListener;

    public FragmentBackstackChangeListenerImpl(FragmentManager fragmentManager, BackstackCallback backstackChangeListener) {
        this.fragmentManager = fragmentManager;
        this.backstackChangeListener = backstackChangeListener;
        lastBackStackEntryCount = fragmentManager.getBackStackEntryCount();
    }

    private boolean wasPushed(int backStackEntryCount) {
        return lastBackStackEntryCount < backStackEntryCount;
    }

    private boolean wasPopped(int backStackEntryCount) {
        return lastBackStackEntryCount > backStackEntryCount;
    }

    private boolean haveFragments() {
        List<Fragment> fragmentList = fragmentManager.getFragments();
        return fragmentList != null && !fragmentList.isEmpty();
    }


    /**
     * If we push a fragment to backstack then parent would be the one before => size - 2
     * If we pop a fragment from backstack logically it should be the last fragment in the list, but in Android popping a fragment just makes list entry null keeping list size intact, thus it's also size - 2
     *
     * @return fragment that is parent to the one that is pushed to or popped from back stack
     */
    private Fragment getParentFragment() {
        List<Fragment> fragmentList = fragmentManager.getFragments();
        return fragmentList.get(Math.max(0, fragmentList.size() - 2));
    }

    @Override
    public void onBackStackChanged() {
        int currentBackStackEntryCount = fragmentManager.getBackStackEntryCount();
        if (haveFragments()) {
            Fragment parentFragment = getParentFragment();

            //will be null if was just popped and was last in the stack
            if (parentFragment != null) {
                if (wasPushed(currentBackStackEntryCount)) {
                    backstackChangeListener.onFragmentPushed(parentFragment);
                } else if (wasPopped(currentBackStackEntryCount)) {
                    backstackChangeListener.onFragmentPopped(parentFragment);
                }
            }
        }

        lastBackStackEntryCount = currentBackStackEntryCount;
    }
}

BackstackCallback.java:

public interface BackstackCallback {
    void onFragmentPushed(Fragment parentFragment);

    void onFragmentPopped(Fragment parentFragment);
}
AAverina
fuente
1

Si un fragmento se coloca en backstack, Android simplemente destruye su vista. La instancia del fragmento en sí no se mata. Una forma sencilla de empezar debería escuchar el evento onViewCreated, y poner la lógica "onResume ()" allí.

boolean fragmentAlreadyLoaded = false;
    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onViewCreated(view, savedInstanceState);

        if (savedInstanceState == null && !fragmentAlreadyLoaded) {
            fragmentAlreadyLoaded = true;

            // Code placed here will be executed once
        }

        //Code placed here will be executed even when the fragment comes from backstack
    }
Franjo
fuente
0

Esta es la respuesta correcta que puede llamar a onResume () siempre que el fragmento esté adjunto a la actividad. Alternativamente, puede usar onAttach y onDetach

iamlukeyb
fuente
1
¿Puedes publicar un ejemplo por favor?
kavie
0

onResume () para el fragmento funciona bien ...

public class listBook extends Fragment {

    private String listbook_last_subtitle;
...

    @Override
       public void onCreate(Bundle savedInstanceState) {

        String thisFragSubtitle = (String) getActivity().getActionBar().getSubtitle();
        listbook_last_subtitle = thisFragSubtitle;
       }
...

    @Override
        public void onResume(){
            super.onResume();
            getActivity().getActionBar().setSubtitle(listbook_last_subtitle);
        }
...
Roshan Poudyal
fuente
0
public abstract class RootFragment extends Fragment implements OnBackPressListener {

 @Override
 public boolean onBackPressed() {
  return new BackPressImpl(this).onBackPressed();
 }

 public abstract void OnRefreshUI();

}


public class BackPressImpl implements OnBackPressListener {

 private Fragment parentFragment;

 public BackPressImpl(Fragment parentFragment) {
  this.parentFragment = parentFragment;
 }

 @Override
 public boolean onBackPressed() {
  ((RootFragment) parentFragment).OnRefreshUI();
 }
}

y la extensión final de su Frament de RootFragment para ver el efecto

Luu Quoc Thang
fuente
0

Mi solución es obtener el título actual de la barra de acciones en el Fragmento antes de establecerlo en el nuevo título. De esta manera, una vez que aparece el Fragmento, puedo volver a ese título.

@Override
public void onResume() {
    super.onResume();
    // Get/Backup current title
    mTitle = ((ActionBarActivity) getActivity()).getSupportActionBar()
            .getTitle();
    // Set new title
    ((ActionBarActivity) getActivity()).getSupportActionBar()
        .setTitle(R.string.this_fragment_title);
}

@Override
public void onDestroy() {
    // Set title back
    ((ActionBarActivity) getActivity()).getSupportActionBar()
        .setTitle(mTitle);

    super.onDestroy();
}
Caramelo Mahendran
fuente
0

He usado enum FragmentTagspara definir todas mis clases de fragmentos.

TAG_FOR_FRAGMENT_A(A.class),
TAG_FOR_FRAGMENT_B(B.class),
TAG_FOR_FRAGMENT_C(C.class)

pasar FragmentTags.TAG_FOR_FRAGMENT_A.name()como etiqueta de fragmento.

y ahora en

@Override
public void onBackPressed(){
   FragmentManager fragmentManager = getFragmentManager();
   Fragment current
   = fragmentManager.findFragmentById(R.id.fragment_container);
    FragmentTags fragmentTag = FragmentTags.valueOf(current.getTag());

  switch(fragmentTag){
    case TAG_FOR_FRAGMENT_A:
        finish();
        break;
   case TAG_FOR_FRAGMENT_B:
        fragmentManager.popBackStack();
        break;
   case default: 
   break;
}
Ali
fuente