Fragmenty włączone Wznów z tylnego stosu

96

Używam pakietu zgodności, aby używać fragmentów z systemem Android 2.2. Używając fragmentów i dodając przejścia między nimi do backstacka, chciałbym uzyskać to samo zachowanie, co onResume aktywności, tj. Zawsze, gdy fragment jest przenoszony na „pierwszy plan” (widoczny dla użytkownika) po wyskoczeniu z backstack, chciałbym, aby jakiś rodzaj wywołania zwrotnego został aktywowany we fragmencie (na przykład w celu wykonania pewnych zmian we współdzielonym zasobie UI).

Widziałem, że nie ma wbudowanego wywołania zwrotnego w ramach fragmentu. czy istnieje dobra praktyka, aby to osiągnąć?

oriharel
źródło
13
Świetne pytanie. Próbuję zmienić tytuł paska akcji w zależności od tego, jaki fragment jest widoczny jako przypadek użycia dla tego scenariusza i wydaje mi się, że brakuje mi wywołania zwrotnego w API.
Manfred Moser,
3
Twoja aktywność może ustawić tytuł, biorąc pod uwagę, że wie, które fragmenty są wyświetlane w dowolnym momencie. Przypuszczam również, że mógłbyś zrobić coś, onCreateViewco spowoduje wezwanie do fragmentu po pyknięciu.
PJL
1
@PJL +1 Zgodnie z odniesieniem, tak powinno być. Jednak wolę sposób słuchania
momo

Odpowiedzi:

116

Z braku lepszego rozwiązania mam to działające dla mnie: Załóżmy, że mam 1 aktywność (MyActivity) i kilka fragmentów, które zastępują się nawzajem (tylko jeden jest widoczny na raz).

W MyActivity dodaj ten detektor:

getSupportFragmentManager().addOnBackStackChangedListener(getListener());

(Jak widać, używam pakietu zgodności).

implementacja 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()zostanie wywołana po naciśnięciu przycisku „Wstecz”. jednak kilka zastrzeżeń:

  1. Zakłada się, że dodałeś wszystkie transakcje do backstack (używając FragmentTransaction.addToBackStack())
  2. Będzie aktywowany przy każdej zmianie stosu (możesz przechowywać inne rzeczy na tylnym stosie, takie jak animacja), więc możesz uzyskać wiele wywołań dla tej samej instancji fragmentu.
oriharel
źródło
3
Powinieneś zaakceptować swoją własną odpowiedź jako poprawną, jeśli zadziałała.
powerj1984
7
Jak to działa pod względem identyfikatora fragmentu, o który pytasz? Czym różni się każdy fragment, a właściwy znajduje się na tylnym stosie?
Manfred Moser,
2
@Warpzit ta metoda nie jest wywoływana, a android docs po cichu przyznają, że nigdy nie zostanie wywołana (jest wywoływana tylko wtedy, gdy aktywność zostanie wznowiona - czyli nigdy, jeśli ta aktywność jest już aktywna)
Adam
2
To śmieszne, że musimy to zrobić! Ale cieszę się, że ktoś inny mógł znaleźć tę „funkcję”
stevebot
3
onResume () NIE jest wywoływany
Marty Miller
33

Trochę zmieniłem sugerowane rozwiązanie. U mnie działa lepiej:

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
źródło
1
proszę wyjaśnić różnicę i dlaczego tego używać.
Chłodnica matematyczna
2
Różnica między moim rozwiązaniem a poprzednim polega na tym, że przy każdej zmianie fragmentu, takiej jak dodanie, zamiana lub cofnięcie się w stos fragmentów, zostanie wywołana metoda „onResume” górnego fragmentu. W tej metodzie nie ma na stałe zakodowanego identyfikatora fragmentu. Zasadniczo wywołuje onResume () we fragmencie, na który patrzysz przy każdej zmianie, bez względu na to, czy był już załadowany do pamięci, czy nie.
Brotoo25
9
Nie ma rekordu dla metody o nazwie getFragments()SupportFragmentManager ... -1: - /
Protostome
1
Wywoływana jest funkcja OnResume (), ale wyświetlany jest pusty ekran… Czy jest jakiś inny sposób rozwiązania tego problemu?
kavie
Myślę, że prowadzi to do wyjątku ArrayOutOfBounds, jeśli wartość backStackEntryCount wynosi 0. Czy się mylę? Próbowałem edytować post, ale został odrzucony.
Mike T
6

Po a popStackBack()możesz użyć następującego wywołania zwrotnego: onHiddenChanged(boolean hidden)w swoim fragmencie

user2230304
źródło
2
Wygląda na to, że nie działa to z fragmentami zgodności aplikacji.
Lo-Tan
Właśnie tego użyłem. Dzięki!
Albert Vila Calvo
Najprostsze rozwiązanie! Po prostu dodaj sprawdzenie, czy fragment jest obecnie widoczny i voila, możesz ustawić tytuł paska aplikacji.
EricH206,
4

Poniższa sekcja w witrynie Android Developers opisuje mechanizm komunikacji Tworzenie wywołań zwrotnych zdarzeń dla działania . Aby zacytować z niego wiersz:

Dobrym sposobem na to jest zdefiniowanie interfejsu wywołania zwrotnego wewnątrz fragmentu i wymaganie, aby aktywność hosta go zaimplementowała. Gdy działanie otrzymuje wywołanie zwrotne za pośrednictwem interfejsu, może w razie potrzeby udostępniać informacje innym fragmentom układu.

Edycja: Fragment ma, onStart(...)który jest wywoływany, gdy fragment jest widoczny dla użytkownika. Podobnie, onResume(...)gdy jest widoczny i aktywnie działa. Są one powiązane z ich odpowiednikami w działalności. Krótko mówiąc: użyjonResume()

PJL
źródło
1
Po prostu szukam metody w klasie Fragment, która jest aktywowana za każdym razem, gdy jest wyświetlana użytkownikowi. czy to z powodu FragmentTransaction.add () / replace () lub FragmentManager.popBackStack ().
oriharel
@oriharel Zobacz zaktualizowaną odpowiedź. Po załadowaniu działania otrzymasz różne wywołania, onAttach, onCreate, onActivityCreated, onStart, onResume. Jeśli wywołałeś `setRetainInstance (true) ', nie otrzymasz onCreate, gdy fragment jest ponownie wyświetlany po kliknięciu wstecz.
PJL
15
onStart () nigdy nie jest wywoływana po kliknięciu „Wstecz” między fragmentami tego samego działania. Wypróbowałem większość callbacków w dokumentacji i żaden z nich nie jest wywoływany w takim scenariuszu. zobacz moją odpowiedź na obejście.
oriharel
hmm z kompatybilną lib v4 Widzę onResume dla mojego fragmentu. Podoba mi się jednak odpowiedź @ oriharel, ponieważ odróżnia ona bycie widocznym przez popBackStack, a nie tylko przeniesienie na pierwszy plan.
PJL
3

Jeśli fragment zostanie umieszczony na backstacku, Android po prostu niszczy jego widok. Sama instancja fragmentu nie jest zabijana. Prostym sposobem na rozpoczęcie powinno być odsłuchanie zdarzenia onViewCreated, czyli umieszczenie tam logiki „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
    }
Franjo
źródło
3

W mojej aktywności onCreate ()

getSupportFragmentManager().addOnBackStackChangedListener(getListener());

Użyj tej metody, aby złapać określony fragment i wywoł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
źródło
nie pracuje dla mnie
Amir Dora.
2

Trochę ulepszone i opakowane w rozwiązanie menedżera.

O czym należy pamiętać. FragmentManager nie jest singletonem, zarządza tylko fragmentami w ramach działania, więc w każdym działaniu będzie nowy. Ponadto, to rozwiązanie jak dotąd nie bierze pod uwagę ViewPagera, który wywołuje metodę setUserVisibleHint () pomagającą kontrolować widoczność fragmentów.

Zapraszam do korzystania z następujących klas, gdy borykasz się z tym problemem (używa iniekcji Dagger2). Aktywne wezwanie:

//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
źródło
0

To jest poprawna odpowiedź, którą możesz wywołać onResume (), pod warunkiem, że fragment jest dołączony do działania. Alternatywnie możesz użyć onAttach i onDetach

iamlukeyb
źródło
1
Czy możesz wysłać przykład?
kavie
0

onResume () dla fragmentu działa dobrze ...

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
źródło
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();
 }
}

i ostatecznie rozszerz Frament z RootFragment, aby zobaczyć efekt

Luu Quoc Thang
źródło
0

Moje obejście polega na uzyskaniu bieżącego tytułu paska akcji we fragmencie przed ustawieniem go na nowy tytuł. W ten sposób po wyskakiwaniu fragmentu mogę wrócić do tego tytułu.

@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
źródło
0

Użyłem enum FragmentTagsdo zdefiniowania wszystkich moich klas fragmentów.

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

przekazać FragmentTags.TAG_FOR_FRAGMENT_A.name()jako znacznik fragmentu.

i teraz

@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
źródło