Jak wznowić fragment z BackStack, jeśli istnieje

151

Uczę się używać fragmentów. Mam trzy instancje, Fragmentktóre są inicjowane w górnej części klasy. Dodaję fragment do takiej czynności:

Deklarowanie i inicjowanie:

Fragment A = new AFragment();
Fragment B = new BFragment();
Fragment C = new CFragment();

Zastąpienie / dodanie:

FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, A);
ft.addToBackStack(null);
ft.commit();

Te fragmenty działają poprawnie. Każdy fragment jest dołączany do ćwiczenia i bez problemu zapisywany na tylnym stosie.

Więc kiedy uruchamiam A, Ca potem B, stos wygląda następująco:

| |
|B|
|C|
|A|
___

A kiedy naciskam przycisk „wstecz”, Bzostaje zniszczony i Cwznowiony.

Ale kiedy uruchamiam fragment Apo raz drugi, zamiast wznowienia z tylnego stosu, jest on dodawany na górze tylnego stosu

| |
|A|
|C|
|A|
___

Ale chcę wznowić Ai zniszczyć wszystkie fragmenty na wierzchu (jeśli takie istnieją). Właściwie podoba mi się domyślne zachowanie stosu wstecznego.

Jak to osiągnąć?

Oczekiwano: ( Anależy wznowić, a górne fragmenty zniszczyć)

| |
| |
| |
|A|
___

Edycja: (sugerowane przez A - C)

Oto mój próbny kod:

private void selectItem(int position) {
        Fragment problemSearch = null, problemStatistics = null;
        FragmentManager manager = getSupportFragmentManager();
        FragmentTransaction ft = manager.beginTransaction();
        String backStateName = null;
        Fragment fragmentName = null;
        boolean fragmentPopped = false;
        switch (position) {
        case 0:
            fragmentName = profile;
            break;
        case 1:
            fragmentName = submissionStatistics;
            break;
        case 2:
            fragmentName = solvedProblemLevel;
            break;
        case 3:
            fragmentName = latestSubmissions;
            break;
        case 4:
            fragmentName = CPExercise;
            break;
        case 5:
            Bundle bundle = new Bundle();
            bundle.putInt("problem_no", problemNo);
            problemSearch = new ProblemWebView();
            problemSearch.setArguments(bundle);
            fragmentName = problemSearch;
            break;
        case 6:
            fragmentName = rankList;
            break;
        case 7:
            fragmentName = liveSubmissions;
            break;
        case 8:
            Bundle bundles = new Bundle();
            bundles.putInt("problem_no", problemNo);
            problemStatistics = new ProblemStatistics();
            problemStatistics.setArguments(bundles);
            fragmentName = problemStatistics;
        default:
            break;
        }
        backStateName = fragmentName.getClass().getName();
        fragmentPopped = manager.popBackStackImmediate(backStateName, 0);
        if (!fragmentPopped) {
            ft.replace(R.id.content_frame, fragmentName);
        }
        ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
        ft.addToBackStack(backStateName);
        ft.commit();

        // I am using drawer layout
        mDrawerList.setItemChecked(position, true);
        setTitle(title[position]);
        mDrawerLayout.closeDrawer(mDrawerList);
    }

Problem polega na tym, kiedy rozpocząć Ai potem B, a następnie naciśnij przycisk „Wstecz”, Bzostanie usunięty i Azostanie wznowione. a ponowne naciśnięcie „wstecz” powinno zamknąć aplikację. Ale pokazuje puste okno i muszę nacisnąć trzeci raz, aby je zamknąć.

Również, kiedy uruchomić A, a następnie B, potem C, potem Bznowu ...

Spodziewany:

| |
| |
|B|
|A|
___

Rzeczywisty:

| |
|B|
|B|
|A|
___

Czy powinienem nadpisać onBackPressed()jakiekolwiek dostosowanie, czy czegoś mi brakuje?

Kaidul
źródło

Odpowiedzi:

279

Czytając dokumentację , istnieje sposób na zrzucenie stosu w oparciu o nazwę transakcji lub identyfikator dostarczony przez zatwierdzenie. Używanie nazwy może być łatwiejsze, ponieważ nie powinno wymagać śledzenia liczby, która może się zmieniać i wzmacnia logikę „unikalnego wpisu na stosie”.

Ponieważ potrzebujesz tylko jednego wpisu stosu wstecznego na Fragment, nadaj nazwę stanu wstecznego nazwę klasy fragmentu (przez getClass().getName()). Następnie przy wymianie Fragmentzastosuj popBackStackImmediate()metodę. Jeśli zwróci true, oznacza to, że na tylnym stosie znajduje się wystąpienie fragmentu. Jeśli nie, faktycznie wykonaj logikę zastępowania fragmentów.

private void replaceFragment (Fragment fragment){
  String backStateName = fragment.getClass().getName();

  FragmentManager manager = getSupportFragmentManager();
  boolean fragmentPopped = manager.popBackStackImmediate (backStateName, 0);

  if (!fragmentPopped){ //fragment not in back stack, create it.
    FragmentTransaction ft = manager.beginTransaction();
    ft.replace(R.id.content_frame, fragment);
    ft.addToBackStack(backStateName);
    ft.commit();
  }
}

EDYTOWAĆ

Problem polega na tym, że kiedy uruchamiam A, a następnie B, a następnie wciskam przycisk Wstecz, B jest usuwany, a A zostaje wznowiony. i ponowne naciśnięcie przycisku Wstecz powinno zamknąć aplikację. Ale pokazuje puste okno i wymaga kolejnego naciśnięcia, aby je zamknąć.

Dzieje się tak, ponieważ FragmentTransactionjest on dodawany do tylnego stosu, aby zapewnić, że później będziemy mogli włożyć fragmenty na wierzch. Szybkim rozwiązaniem jest zastąpienie onBackPressed()i zakończenie działania, jeśli tylny stos zawiera tylko 1Fragment

@Override
public void onBackPressed(){
  if (getSupportFragmentManager().getBackStackEntryCount() == 1){
    finish();
  }
  else {
    super.onBackPressed();
  }
}

Jeśli chodzi o zduplikowane wpisy stosu wstecznego, twoja instrukcja warunkowa, która zastępuje fragment, jeśli nie został on usunięty, jest wyraźnie inna niż to, co zawiera mój oryginalny fragment kodu. To, co robisz, to dodawanie do tylnego stosu, niezależnie od tego, czy tylny stos został usunięty, czy nie.

Coś takiego powinno być bliżej tego, czego chcesz:

private void replaceFragment (Fragment fragment){
  String backStateName =  fragment.getClass().getName();
  String fragmentTag = backStateName;

  FragmentManager manager = getSupportFragmentManager();
  boolean fragmentPopped = manager.popBackStackImmediate (backStateName, 0);

  if (!fragmentPopped && manager.findFragmentByTag(fragmentTag) == null){ //fragment not in back stack, create it.
    FragmentTransaction ft = manager.beginTransaction();
    ft.replace(R.id.content_frame, fragment, fragmentTag);
    ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
    ft.addToBackStack(backStateName);
    ft.commit();
  } 
}

Warunek został nieco zmieniony, ponieważ wybranie tego samego fragmentu, gdy był widoczny, spowodowało również powielenie wpisów.

Realizacja:

Zdecydowanie radzę nie replaceFragment()rozbierać zaktualizowanej metody na części, tak jak w kodzie. Cała logika jest zawarta w tej metodzie, a ruchome części mogą powodować problemy.

Oznacza to, że powinieneś skopiować zaktualizowaną replaceFragment()metodę do swojej klasy, a następnie zmienić

backStateName = fragmentName.getClass().getName();
fragmentPopped = manager.popBackStackImmediate(backStateName, 0);
if (!fragmentPopped) {
            ft.replace(R.id.content_frame, fragmentName);
}
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.addToBackStack(backStateName);
ft.commit();

więc jest po prostu

replaceFragment (fragmentName);

EDYCJA # 2

Aby zaktualizować szufladę po zmianie stosu wstecznego, utwórz metodę, która akceptuje w fragmencie i porównuje nazwy klas. Jeśli coś pasuje, zmień tytuł i wybór. Dodaj również OnBackStackChangedListeneri niech wywoła metodę aktualizacji, jeśli istnieje prawidłowy fragment.

Na przykład w działaniu onCreate()dodaj

getSupportFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener() {

  @Override
  public void onBackStackChanged() {
    Fragment f = getSupportFragmentManager().findFragmentById(R.id.content_frame);
    if (f != null){
      updateTitleAndDrawer (f);
    }

  }
});

I inna metoda:

private void updateTitleAndDrawer (Fragment fragment){
  String fragClassName = fragment.getClass().getName();

  if (fragClassName.equals(A.class.getName())){
    setTitle ("A");
    //set selected item position, etc
  }
  else if (fragClassName.equals(B.class.getName())){
    setTitle ("B");
    //set selected item position, etc
  }
  else if (fragClassName.equals(C.class.getName())){
    setTitle ("C");
    //set selected item position, etc
  }
}

Teraz za każdym razem, gdy zmieni się stos, tytuł i zaznaczona pozycja będą odzwierciedlać to, co widać Fragment.

A - C
źródło
Dziękuję za odpowiedź. :) Działa częściowo. Redagowałem moje pytanie z postępem. Proszę spojrzeć)
Kaidul
2
@RishabhSrivastava pamiętaj, że "pop" zwykle niszczy najwyższy fragment. Od tego, która metoda zostanie wywołana jako pierwsza, zależy - spójrz na cykl życia fragmentu. W twoim przypadku użyłbym znaku OnBackstackChangedListener, abyś mógł zagwarantować, że masz do czynienia z odpowiednim fragmentem.
A - C
2
@RishabhSrivastava Dlatego też zasugerowałem OnBackstackChangedListener.
A - C
1
@AlexanderFarber Masz rację. Kiedy udzieliłem tej odpowiedzi, nie myślałem o tym instanceof:)
A - C
2
Twoje rozwiązanie brzmi dobrze, ale jeśli masz trzy fragmenty i klikniesz je A-B-C-Bi raz wrócisz, wrócisz do, Aa nie do C. To dlatego, że popBackStackImmediatenie tylko wyskakuje podany tag, ale także wszystko powyżej. Czy jest tam jakaś praca?
Raeglan
10

Myślę, że ta metoda może rozwiązać twój problem:

public static void attachFragment ( int fragmentHolderLayoutId, Fragment fragment, Context context, String tag ) {


    FragmentManager manager = ( (AppCompatActivity) context ).getSupportFragmentManager ();
    FragmentTransaction ft = manager.beginTransaction ();

    if (manager.findFragmentByTag ( tag ) == null) { // No fragment in backStack with same tag..
        ft.add ( fragmentHolderLayoutId, fragment, tag );
        ft.addToBackStack ( tag );
        ft.commit ();
    }
    else {
        ft.show ( manager.findFragmentByTag ( tag ) ).commit ();
    }
}

który został pierwotnie opublikowany w tym pytaniu

erluxman
źródło
Daj mi znać, jeśli odpowiadanie w ten sposób jest niezgodne z zasadami, które chciałem tylko ułatwić ludziom zobaczenie tego posta. Dzięki…
erluxman
Nie jestem pewien, czy jest to niezgodne z zasadami (prawdopodobnie nie), ale jest to zdecydowanie pomocne
Kathir
1
Jaki jest pożytek z trzeciej linii? -> manager.findFragmentByTag (tag); Wygląda na to, że nic nie robi ...
Rik van Velzen
3

Krok 1: Zaimplementuj interfejs ze swoją klasą aktywności

public class AuthenticatedMainActivity extends Activity implements FragmentManager.OnBackStackChangedListener{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        .............
        FragmentManager fragmentManager = getFragmentManager();           
        fragmentManager.beginTransaction().add(R.id.frame_container,fragment, "First").addToBackStack(null).commit();
    }

    private void switchFragment(Fragment fragment){            
      FragmentManager fragmentManager = getFragmentManager();
      fragmentManager.beginTransaction()
        .replace(R.id.frame_container, fragment).addToBackStack("Tag").commit();
    }

    @Override
    public void onBackStackChanged() {
    FragmentManager fragmentManager = getFragmentManager();

    System.out.println("@Class: SummaryUser : onBackStackChanged " 
            + fragmentManager.getBackStackEntryCount());

    int count = fragmentManager.getBackStackEntryCount();

    // when a fragment come from another the status will be zero
    if(count == 0){

        System.out.println("again loading user data");

        // reload the page if user saved the profile data

        if(!objPublicDelegate.checkNetworkStatus()){

            objPublicDelegate.showAlertDialog("Warning"
                    , "Please check your internet connection");

        }else {

            objLoadingDialog.show("Refreshing data..."); 

            mNetworkMaster.runUserSummaryAsync();
        }

        // IMPORTANT: remove the current fragment from stack to avoid new instance
        fragmentManager.removeOnBackStackChangedListener(this);

    }// end if
   }       
}

Krok 2: Kiedy wywołasz inny fragment, dodaj tę metodę:

String backStateName = this.getClass().getName();

FragmentManager fragmentManager = getFragmentManager();
fragmentManager.addOnBackStackChangedListener(this); 

Fragment fragmentGraph = new GraphFragment();
Bundle bundle = new Bundle();
bundle.putString("graphTag",  view.getTag().toString());
fragmentGraph.setArguments(bundle);

fragmentManager.beginTransaction()
.replace(R.id.content_frame, fragmentGraph)
.addToBackStack(backStateName)
.commit();
Vinod Joshi
źródło
2

Wiem, że jest już dość późno, aby odpowiedzieć na to pytanie, ale sam rozwiązałem ten problem i pomyślałem, że warto podzielić się nim ze wszystkimi. ”

public void replaceFragment(BaseFragment fragment) {
    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
    final FragmentManager fManager = getSupportFragmentManager();
    BaseFragment fragm = (BaseFragment) fManager.findFragmentByTag(fragment.getFragmentTag());
    transaction.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_left, R.anim.enter_from_left, R.anim.exit_to_right);

    if (fragm == null) {  //here fragment is not available in the stack
        transaction.replace(R.id.container, fragment, fragment.getFragmentTag());
        transaction.addToBackStack(fragment.getFragmentTag());
    } else { 
        //fragment was found in the stack , now we can reuse the fragment
        // please do not add in back stack else it will add transaction in back stack
        transaction.replace(R.id.container, fragm, fragm.getFragmentTag()); 
    }
    transaction.commit();
}

I w onBackPressed ()

 @Override
public void onBackPressed() {
    if(getSupportFragmentManager().getBackStackEntryCount()>1){
        super.onBackPressed();
    }else{
        finish();
    }
}
Vivek Pratap Singh
źródło
1
@ X-Black ... BaseFragment jest klasą bazową dla każdego fragmentu używanego w aplikacji.
Vivek Pratap Singh
0
getFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {

    @Override
    public void onBackStackChanged() {

        if(getFragmentManager().getBackStackEntryCount()==0) {
            onResume();
        }    
    }
});
gushedaoren
źródło
0

Łatwiejszym rozwiązaniem będzie zmiana tej linii

ft.replace(R.id.content_frame, A); do ft.add(R.id.content_frame, A);

Wewnątrz swojego układu XML użyj

  android:background="@color/white"
  android:clickable="true"
  android:focusable="true"

Clickable oznacza, że ​​można go kliknąć za pomocą urządzenia wskazującego lub dotknąć za pomocą urządzenia dotykowego.

Focusableoznacza, że ​​może uzyskać fokus z urządzenia wejściowego, takiego jak klawiatura. Urządzenia wejściowe, takie jak klawiatury, nie mogą zdecydować, do którego widoku wysłać zdarzenia wejściowe na podstawie samych danych wejściowych, więc wysyłają je do widoku, który ma fokus.

Hossam Hassan
źródło