Frammenti su Riprendi dallo stack precedente


94

Sto utilizzando il pacchetto di compatibilità per utilizzare Fragments con Android 2.2. Quando si utilizzano frammenti e si aggiungono transizioni tra di essi al backstack, mi piacerebbe ottenere lo stesso comportamento di onResume di un'attività, ovvero ogni volta che un frammento viene portato in "primo piano" (visibile all'utente) dopo essere uscito dal backstack, vorrei che fosse attivato un qualche tipo di callback all'interno del frammento (per eseguire determinate modifiche su una risorsa dell'interfaccia utente condivisa, ad esempio).

Ho visto che non esiste un callback integrato nel framework dei frammenti. c'è una buona pratica per raggiungere questo obiettivo?


13
Ottima domanda. Sto provando a cambiare il titolo della barra delle azioni a seconda di quale frammento è visibile come caso d'uso per questo scenario e mi sembra un callback mancante nell'API.
Manfred Moser

3
La tua attività può impostare il titolo dato che sa quali frammenti vengono visualizzati in qualsiasi momento. Inoltre suppongo che potresti fare qualcosa in onCreateViewcui verrà chiamato il frammento dopo un pop.
PJL

1
@PJL +1 Secondo il riferimento, questo è il modo in cui dovrebbe essere fatto. Tuttavia preferisco il modo dell'ascoltatore
momo

Risposte:


115

Per mancanza di una soluzione migliore, ho trovato questo che funziona per me: supponiamo di avere 1 attività (MyActivity) e pochi frammenti che si sostituiscono a vicenda (solo uno è visibile alla volta).

In MyActivity, aggiungi questo listener:

getSupportFragmentManager().addOnBackStackChangedListener(getListener());

(Come puoi vedere sto usando il pacchetto di compatibilità).

implementazione 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()verrà chiamato dopo aver premuto "Indietro". alcuni avvertimenti però:

  1. Si presume che tu abbia aggiunto tutte le transazioni al backstack (utilizzando FragmentTransaction.addToBackStack())
  2. Verrà attivato a ogni modifica dello stack (è possibile memorizzare altre cose nello stack posteriore come l'animazione) in modo da poter ottenere più chiamate per la stessa istanza di frammento.

3
Dovresti accettare la tua risposta come corretta se ha funzionato per te.
powerj1984

7
Come funziona in termini di ID frammento da cui stai interrogando? In che modo è diverso per ogni frammento e quello giusto si trova nello stack posteriore?
Manfred Moser

2
@Warpzit quel metodo non viene chiamato e i documenti Android ammettono tranquillamente che non verrà mai chiamato (viene chiamato solo quando l'attività viene ripresa, il che non è mai, se l'attività è già attiva)
Adam

2
Ridicolo che dobbiamo farlo! Ma sono contento che qualcun altro sia riuscito a trovare questa "funzionalità"
stevebot

3
onResume () NON si chiama
Marty Miller

33

Ho cambiato leggermente la soluzione suggerita. Funziona meglio per me in questo modo:

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

1
per favore spiega la differenza e perché usarlo.
Math chiller

2
La differenza tra la mia soluzione e quella precedente è che ad ogni cambio di frammento come l'aggiunta, la sostituzione o il ritorno all'indietro nella pila dei frammenti, verrà chiamato il metodo "onResume" del frammento superiore. Non esiste un ID frammento codificato in questo metodo. Fondamentalmente chiama onResume () nel frammento che stai guardando ad ogni modifica, non importa se era già caricato in memoria o meno.
Brotoo25

9
Nessun record per un metodo chiamato getFragments()in SupportFragmentManager ... -1: - /
Protostome

1
Viene chiamato OnResume (), ma viene visualizzata una schermata vuota. Qualche altro modo per risolvere questo problema?
kavie

Penso che questo porti a un'eccezione ArrayOutOfBounds se backStackEntryCount è 0. Mi sbaglio? Ho provato a modificare il post ma è stato rifiutato.
Mike T

6

Dopo un popStackBack()è possibile utilizzare il seguente callback: onHiddenChanged(boolean hidden)all'interno del frammento


2
Questo non sembra funzionare con i frammenti di compatibilità delle app.
Lo-Tan

È quello che ho usato. Grazie!
Albert Vila Calvo

La soluzione più semplice! Basta aggiungere un controllo se il frammento è attualmente visibile e voilà, puoi impostare il titolo dell'appbar.
EricH206

4

La sezione seguente in Android Developers descrive un meccanismo di comunicazione che crea callback di eventi per l'attività . Per citare una riga da esso:

Un buon modo per farlo è definire un'interfaccia di callback all'interno del frammento e richiedere che l'attività dell'host la implementi. Quando l'attività riceve una richiamata tramite l'interfaccia, può condividere le informazioni con altri frammenti nel layout, se necessario.

Modifica: il frammento ha un onStart(...)che viene richiamato quando il frammento è visibile all'utente. Allo stesso modo un onResume(...)quando visibile e attivamente in esecuzione. Questi sono legati alle loro controparti di attività. In breve: utilizzareonResume()


1
Sto solo cercando un metodo nella classe Fragment che viene attivato ogni volta che viene mostrato all'utente. sia a causa di un FragmentTransaction.add () / replace (), o di un FragmentManager.popBackStack ().
oriharel

@oriharel Vedi la risposta aggiornata. Quando l'attività viene caricata riceverai varie chiamate, onAttach, onCreate, onActivityCreated, onStart, onResume. Se hai chiamato `setRetainInstance (true) ', non otterrai un onCreate quando il frammento viene mostrato nuovamente facendo clic su Indietro.
PJL

15
onStart () non viene mai chiamato quando si fa clic su "Indietro" tra i frammenti nella stessa attività. Ho provato la maggior parte dei callback nella documentazione e nessuno di loro è stato chiamato in tale scenario. vedere la mia risposta per una soluzione alternativa.
oriharel

hmm con compat lib v4 vedo un onResume per il mio frammento. Mi piace la risposta di @oriharel, tuttavia, poiché distingue tra il diventare visibili attraverso un popBackStack piuttosto che essere semplicemente portati in primo piano.
PJL

3

Nella mia attività su Crea ()

getSupportFragmentManager().addOnBackStackChangedListener(getListener());

Usa questo metodo per catturare frammenti specifici e chiamare 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;
    }

2

Un po 'migliorato e inserito in una soluzione manageriale.

Cose da tenere a mente. FragmentManager non è un singleton, gestisce solo i frammenti all'interno di Activity, quindi in ogni attività sarà nuovo. Inoltre, questa soluzione finora non prende in considerazione ViewPager che chiama il metodo setUserVisibleHint () aiutando a controllare la visibilità dei frammenti.

Sentiti libero di usare le seguenti classi quando affronti questo problema (usa l'iniezione Dagger2). Chiamata in attività:

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

1

Se un frammento viene messo in backstack, Android ne distrugge semplicemente la visualizzazione. L'istanza del frammento stessa non viene uccisa. Un modo semplice per iniziare dovrebbe essere quello di ascoltare l'evento onViewCreated, inserire lì la logica "onResume ()".

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
    }

0

Questa è la risposta corretta che puoi chiamare onResume () a condizione che il frammento sia allegato all'attività. In alternativa puoi usare onAttach e onDetach


1
Puoi pubblicare un esempio per favore?
kavie

0

onResume () per il frammento funziona bene ...

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

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

e infine estendi il tuo Frament da RootFragment per vedere l'effetto


0

La mia soluzione alternativa è ottenere il titolo corrente della barra delle azioni nel frammento prima di impostarlo sul nuovo titolo. In questo modo, una volta che il frammento è stato estratto, posso tornare a quel titolo.

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

0

Ho usato enum FragmentTagsper definire tutte le mie classi di frammenti.

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

passare FragmentTags.TAG_FOR_FRAGMENT_A.name()come tag di frammento.

e adesso

@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;
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.