Fragmente onResume vom Backstack

94

Ich verwende das Kompatibilitätspaket, um Fragmente mit Android 2.2 zu verwenden. Wenn Sie Fragmente verwenden und Übergänge zwischen ihnen zum Backstack hinzufügen, möchte ich das gleiche Verhalten wie onResume einer Aktivität erzielen, dh immer dann, wenn ein Fragment nach dem Verlassen des Stacks in den "Vordergrund" (für den Benutzer sichtbar) gebracht wird Backstack, ich möchte, dass eine Art Rückruf innerhalb des Fragments aktiviert wird (um beispielsweise bestimmte Änderungen an einer gemeinsam genutzten UI-Ressource vorzunehmen).

Ich habe gesehen, dass es innerhalb des Fragment-Frameworks keinen eingebauten Rückruf gibt. Gibt es eine gute Praxis, um dies zu erreichen?

Oriharel
quelle
13
Gute Frage. Ich versuche, den Titel der Aktionsleiste zu ändern, je nachdem, welches Fragment als Anwendungsfall für dieses Szenario sichtbar ist, und es scheint mir ein fehlender Rückruf in der API zu sein.
Manfred Moser
3
Ihre Aktivität kann den Titel festlegen, da sie weiß, welche Fragmente gleichzeitig angezeigt werden. Ich nehme auch an, Sie könnten etwas tun, bei onCreateViewdem nach einem Pop nach dem Fragment gerufen wird.
PJL
1
@PJL +1 Laut Referenz sollte dies so erfolgen. Ich bevorzuge jedoch den Hörer-Weg
Momo

Antworten:

115

Da es keine bessere Lösung gibt, funktioniert dies für mich: Angenommen, ich habe 1 Aktivität (MyActivity) und wenige Fragmente, die sich gegenseitig ersetzen (jeweils nur eines ist sichtbar).

Fügen Sie in MyActivity diesen Listener hinzu:

getSupportFragmentManager().addOnBackStackChangedListener(getListener());

(Wie Sie sehen, verwende ich das Kompatibilitätspaket).

getListener-Implementierung:

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()wird aufgerufen, nachdem ein "Zurück" gedrückt wurde. Einige Einschränkungen:

  1. Es wird davon ausgegangen, dass Sie alle Transaktionen zum Backstack hinzugefügt haben (using FragmentTransaction.addToBackStack())
  2. Es wird bei jedem Stapelwechsel aktiviert (Sie können andere Inhalte im Backstack speichern, z. B. Animationen), sodass Sie möglicherweise mehrere Aufrufe für dieselbe Fragmentinstanz erhalten.
Oriharel
quelle
3
Sie sollten Ihre eigene Antwort als richtig akzeptieren, wenn sie für Sie funktioniert hat.
powerj1984
7
Wie funktioniert das in Bezug auf die Fragment-ID, nach der Sie abfragen? Wie unterscheidet es sich für jedes Fragment und das richtige befindet sich im hinteren Stapel?
Manfred Moser
2
@Warpzit diese Methode wird nicht aufgerufen, und Android-Dokumente geben stillschweigend zu, dass sie niemals aufgerufen wird (sie wird nur aufgerufen, wenn die Aktivität wieder aufgenommen wird - was niemals der Fall ist, wenn diese Aktivität bereits aktiv ist)
Adam
2
Lächerlich, dass wir das tun müssen! Aber froh, dass jemand anderes dieses "Feature" finden konnte
Stevebot
3
onResume () heißt NICHT
Marty Miller
33

Ich habe die vorgeschlagene Lösung ein wenig geändert. Funktioniert bei mir so besser:

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
quelle
1
Bitte erläutern Sie den Unterschied und warum.
Mathe Chiller
2
Der Unterschied zwischen meiner und der vorherigen Lösung besteht darin, dass bei jeder Fragmentänderung wie Hinzufügen, Ersetzen oder Rückwärtsgehen im Fragmentstapel die "onResume" -Methode des oberen Fragments aufgerufen wird. Bei dieser Methode ist keine Fragment-ID fest codiert. Grundsätzlich ruft onResume () in dem Fragment auf, das Sie bei jeder Änderung betrachten, unabhängig davon, ob es bereits in den Speicher geladen wurde oder nicht.
Brotoo25
9
Es gibt keinen Datensatz für eine Methode, die getFragments()in SupportFragmentManager aufgerufen wird ... -1: - /
Protostome
1
OnResume () wird aufgerufen. Aber ich erhalte einen leeren Bildschirm. Gibt es eine andere Möglichkeit, dieses Problem zu beheben?
Kavie
Ich denke, dies führt zu einer ArrayOutOfBounds-Ausnahme, wenn backStackEntryCount 0 ist. Irre ich mich? Es wurde versucht, den Beitrag zu bearbeiten, aber er wurde abgelehnt.
Mike T
6

Nach a können popStackBack()Sie den folgenden Rückruf verwenden: onHiddenChanged(boolean hidden)innerhalb Ihres Fragments

user2230304
quelle
2
Dies scheint mit App-kompatiblen Fragmenten nicht zu funktionieren.
Lo-Tan
Das habe ich benutzt. Vielen Dank!
Albert Vila Calvo
Die einfachste Lösung! Fügen Sie einfach eine Überprüfung hinzu, ob das Fragment derzeit sichtbar ist, und voila, Sie können den Appbar-Titel festlegen.
EricH206
4

Der folgende Abschnitt bei Android Developers beschreibt einen Kommunikationsmechanismus, der Ereignisrückrufe für die Aktivität erstellt . Um eine Zeile daraus zu zitieren:

Eine gute Möglichkeit, dies zu tun, besteht darin, eine Rückrufschnittstelle innerhalb des Fragments zu definieren und zu verlangen, dass die Hostaktivität diese implementiert. Wenn die Aktivität über die Schnittstelle einen Rückruf erhält, kann sie die Informationen nach Bedarf mit anderen Fragmenten im Layout teilen.

Bearbeiten: Das Fragment hat eine, onStart(...)die aufgerufen wird, wenn das Fragment für den Benutzer sichtbar ist. Ebenso ein onResume(...)wenn sichtbar und aktiv laufend. Diese sind an ihre Aktivitätsgegenstücke gebunden. Kurzum: verwendenonResume()

PJL
quelle
1
Ich suche nur nach einer Methode in der Fragment-Klasse, die aktiviert wird, wenn sie dem Benutzer angezeigt wird. ob aufgrund eines FragmentTransaction.add () / replace () oder eines FragmentManager.popBackStack ().
Oriharel
@oriharel Siehe aktualisierte Antwort. Wenn die Aktivität geladen ist, erhalten Sie verschiedene Aufrufe: onAttach, onCreate, onActivityCreated, onStart, onResume. Wenn Sie "setRetainInstance (true)" aufgerufen haben, erhalten Sie kein onCreate, wenn das Fragment beim Zurückklicken erneut angezeigt wird.
PJL
15
onStart () wird niemals aufgerufen, wenn zwischen Fragmenten derselben Aktivität auf "Zurück" geklickt wird. Ich habe die meisten Rückrufe in der Dokumentation ausprobiert und keiner von ihnen wird in einem solchen Szenario aufgerufen. Siehe meine Antwort für eine Problemumgehung.
Oriharel
hmm mit kompatibler lib v4 Ich sehe einen onResume für mein Fragment. Ich mag die Antwort von @ oriharel, da sie unterscheidet, ob man durch einen popBackStack sichtbar wird oder nur in den Vordergrund gerückt wird.
PJL
3

In meiner Aktivität onCreate ()

getSupportFragmentManager().addOnBackStackChangedListener(getListener());

Verwenden Sie diese Methode, um ein bestimmtes Fragment abzufangen und onResume () aufzurufen.

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
quelle
2

Ein wenig verbessert und in eine Manager-Lösung eingewickelt.

Dinge zu beachten. FragmentManager ist kein Singleton, sondern verwaltet nur Fragmente innerhalb der Aktivität, sodass es in jeder Aktivität neu ist. Bei dieser Lösung wird ViewPager bisher nicht berücksichtigt, da die Methode setUserVisibleHint () aufgerufen wird, um die Sichtbarkeit von Fragmenten zu steuern.

Wenn Sie sich mit diesem Problem befassen, können Sie die folgenden Klassen verwenden (verwendet Dagger2-Injektion). Aktivität aufrufen:

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

Wenn ein Fragment auf den Backstack gelegt wird, zerstört Android einfach seine Ansicht. Die Fragmentinstanz selbst wird nicht getötet. Eine einfache Möglichkeit zum Starten besteht darin, das Ereignis onViewCreated abzuhören und dort die Logik "onResume ()" zu platzieren.

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
quelle
0

Dies ist die richtige Antwort, die Sie onResume () aufrufen können, sofern das Fragment an die Aktivität angehängt ist. Alternativ können Sie onAttach und onDetach verwenden

iamlukeyb
quelle
1
Können Sie bitte ein Beispiel posten?
Kavie
0

onResume () für das Fragment funktioniert gut ...

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

und schließlich Umfang dein Frament von RootFragment, um Wirkung zu sehen

Luu Quoc Thang
quelle
0

Meine Problemumgehung besteht darin, den aktuellen Titel der Aktionsleiste im Fragment abzurufen, bevor Sie den neuen Titel festlegen. Auf diese Weise kann ich nach dem Aufspringen des Fragments wieder zu diesem Titel zurückkehren.

@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();
}
Mahendran Candy
quelle
0

Ich habe enum verwendet FragmentTags, um alle meine Fragmentklassen zu definieren.

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

bestehen FragmentTags.TAG_FOR_FRAGMENT_A.name() als Fragment - Tag.

und jetzt weiter

@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
quelle