fragment androida - jak zapisać stany widoków we fragmencie, gdy inny fragment jest na niego wypychany

137

W FragAAndroidzie fragment (powiedzmy ) zostanie dodany do stosu, a inny fragment (powiedzmy FragB) znajdzie się na górze. Teraz po uderzeniu wraca FragAna górę i onCreateView()zostaje wywołany. Teraz byłem FragAw szczególnym stanie, zanim FragBzostałem zepchnięty na to.

Moje pytanie brzmi: jak mogę przywrócić FragApoprzedni stan? Czy istnieje sposób na zapisanie stanu (na przykład w pakiecie), a jeśli tak, to którą metodę należy zastąpić?

pankajagarwal
źródło

Odpowiedzi:

98

W przewodniku po fragmentach Przykład FragmentList można znaleźć:

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("curChoice", mCurCheckPosition);
}

Które możesz później użyć w ten sposób:

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    if (savedInstanceState != null) {
        // Restore last state for checked position.
        mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
    }
}

Jestem początkującym w Fragments, ale wygląda na to, że rozwiązanie twojego problemu;) OnActivityCreated jest wywoływane po powrocie fragmentu z tylnego stosu.

ania
źródło
18
Nie mogłem zmusić tego do działania. SaveInstanceState zawsze miał wartość null. Dodaję fragment za pomocą układu xml. Musiałem zmienić mCurCheckPosition na statyczną, a następnie działa, ale wydaje się hacky.
scottyab
57
nie wywołuje onSaveInstanceState - dlaczego miałby to robić? Więc to podejście nie działa.
północ
10
Czy to podejście naprawdę zadziała w przypadku, gdy chcemy zachować stan fragmentu podczas powrotu z innego fragmentu w tym samym działaniu? onSaveInstanceState () jest wywoływana tylko przy zdarzeniach Activity onPause / onStop. Zgodnie z dokumentacją: „Również podobnie jak w przypadku działania, można zachować stan fragmentu za pomocą paczki, na wypadek gdyby proces działania został zabity i trzeba przywrócić stan fragmentu po odtworzeniu działania. Stan można zapisać podczas wywołania zwrotnego onSaveInstanceState () fragmentu i przywróć go podczas onCreate (), onCreateView () lub onActivityCreated (). "
Paramvir Singh
26
Dla przypomnienia, to podejście jest błędne i nie powinno być blisko liczby pozytywnych głosów, jakie ma. onSaveInstanceStatejest wywoływana tylko wtedy, gdy odpowiadające jej działanie jest również zamykane.
Martin Konecny
16
onSaveInstanceState () jest wywoływana onle, gdy nastąpiły zmiany konfiguracji i aktywność została zniszczona, ta odpowiedź jest nieprawidłowa
Tadas Valaitis
83

Fragment onSaveInstanceState(Bundle outState)nigdy nie zostanie wywołany, chyba że aktywność fragmentu wezwie go na siebie i dołączone fragmenty. Dlatego ta metoda nie zostanie wywołana, dopóki coś (zazwyczaj rotacja) nie wymusi działania SaveInstanceStatei nie przywróci jej później. Ale jeśli masz tylko jedno działanie i duży zestaw fragmentów w nim (przy intensywnym użyciu replace) i aplikacja działa tylko w jednej orientacji, działanie onSaveInstanceState(Bundle outState)może nie być wywoływane przez długi czas.

Znam trzy możliwe obejścia.

Pierwszy:

użyj argumentów fragmentu do przechowywania ważnych danych:

public class FragmentA extends Fragment {
    private static final String PERSISTENT_VARIABLE_BUNDLE_KEY = "persistentVariable";

    private EditText persistentVariableEdit;

    public FragmentA() {
        setArguments(new Bundle());
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_a, null);

        persistentVariableEdit = (EditText) view.findViewById(R.id.editText);

        TextView proofTextView = (TextView) view.findViewById(R.id.textView);

        Bundle mySavedInstanceState = getArguments();
        String persistentVariable = mySavedInstanceState.getString(PERSISTENT_VARIABLE_BUNDLE_KEY);

        proofTextView.setText(persistentVariable);


        view.findViewById(R.id.btnPushFragmentB).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                getFragmentManager()
                        .beginTransaction()
                        .replace(R.id.frameLayout, new FragmentB())
                        .addToBackStack(null)
                        .commit();
            }
        });

        return view;
    }

    @Override
    public void onPause() {
        super.onPause();
        String persistentVariable = persistentVariableEdit.getText().toString();

        getArguments().putString(PERSISTENT_VARIABLE_BUNDLE_KEY, persistentVariable);
    }
}

Drugi, ale mniej pedantyczny sposób - trzymaj zmienne w singletonach

Trzeci - nie replace()fragmentuje, ale zamiast tego add()/ show()/ hide()je.

Fyodor Volchyok
źródło
3
Najlepsze rozwiązanie, gdy Fragment.onSaveInstanceState()nikt nie dzwonił. Po prostu zapisz własne dane do argumentu, w tym elementy w widoku listy lub tylko ich identyfikatory (jeśli masz inny scentralizowany menedżer danych). Nie ma potrzeby zapisywania pozycji widoku listy - to zostało zapisane i przywrócone automatycznie.
John Pang
Próbowałem użyć twojego przykładu w mojej aplikacji, ale to: String persistentVariable = mySavedInstanceState.getString(PERSISTENT_VARIABLE_BUNDLE_KEY);jest zawsze null. Jaki jest problem?
fragon
Użycie fragmentu getArguments()jest ZDECYDOWO właściwą drogą, w tym zagnieżdżonymi fragmentami w ViewPager. Używam 1 działania i zamieniam wiele fragmentów we / wy, a to działa idealnie. Oto prosty test pozwalający zweryfikować proponowane rozwiązanie: 1) przejdź od fragmentu A do fragmentu B; 2) dwukrotnie zmień orientację urządzenia; 3) naciśnij przycisk powrotu na urządzeniu.
Andy H.
Wypróbowałem pierwsze podejście, ale nie zadziałało. getArguments () zawsze zwraca null. Ma to również sens, ponieważ fragment jest zastępowany i w onCreate () ustawiasz nowy pakiet, aby stary pakiet został utracony. Czego mi brakuje i czy się mylę?
Zvi
@Zvi, napisałem ten kod półtora roku temu i nie pamiętam wszystkich szczegółów, ale jak pamiętam, replace nie odtwarza fragmentów, fragment jest odtwarzany tylko wtedy, gdy utworzyłeś nową instancję ze swojego kodu. W tym przypadku oczywiście konstruktor wywołał i setArguments(new Bundle());nadpisał stary Bundle. Dlatego upewnij się, że utworzyłeś fragment tylko raz, a następnie użyj tej instancji zamiast tworzenia za każdym razem nowego.
Fiodor Volchyok
20

Zwróć uwagę, że jeśli pracujesz z fragmentami za pomocą ViewPagera, jest to całkiem łatwe. Wystarczy tylko zadzwonić do tej metody: setOffscreenPageLimit().

Zgodnie z dokumentacją:

Ustaw liczbę stron, które powinny zostać zachowane po obu stronach bieżącej strony w hierarchii widoków w stanie bezczynności. Strony wykraczające poza ten limit zostaną w razie potrzeby odtworzone z adaptera.

Podobny problem tutaj

przeddzień
źródło
5
To jest inne. setOffScreenPageLimit działa jak cache (czyli ile stron ma obsłużyć ViewPager w danym momencie), ale nie jest używany do zapisywania stanu fragmentu.
Renaud Mathieu
W moim przypadku działało z setOffscreenPageLimit () - mimo zniszczenia fragmentów stan widoku został zapisany i przywrócony.
Davincho
Dzięki, też mi pomogłem.
Elijah
Wiele lat później i nadal jest to aktualne. Chociaż tak naprawdę nie odpowiada na pytanie, rozwiązuje problem
Supreme Dolphin
19

Po prostu raz nadmuchaj swój widok.

Przykład:

public class AFragment extends Fragment {

private View mRootView;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    if(mRootView==null){
        mRootView = inflater.inflate(R.id.fragment_a, container, false);
        //......
    }
    return mRootView;
}

}

Lym Zoy
źródło
Powinien też zachować istniejące fragmenty w tablicy czy coś
Amir
Słyszałem, że utrzymywanie odwołania do rootView fragmentu jest złą praktyką - może spowodować przecieki [potrzebne źródło]?
giraffe.guru
1
@ giraffe.guru Fragmentodnosi się do jego głównego widoku, nie zrobi tego. Podczas gdy odwołanie przez niektóre elementy GC-root , podobnie jak globalna właściwość statyczna, będzie zmienną wątku inną niż ui. FragmentPrzykład nie GC głównego, dzięki czemu mogą być zbierane śmieci. Tak samo będzie z jego głównym poglądem.
Lym Zoy
Ty tego samego dnia.
Vijendra patidar
Słodkie i proste.
Rupam Das
8

Pracowałem z bardzo podobnym problemem. Ponieważ wiedziałem, że będę często wracał do poprzedniego fragmentu, sprawdziłem, czy fragment .isAdded()jest prawdziwy, a jeśli tak, to zamiast transaction.replace()robić a transaction.show(). Dzięki temu fragment nie zostanie odtworzony, jeśli jest już na stosie - nie jest potrzebne zapisywanie stanu.

Fragment target = <my fragment>;
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
if(target.isAdded()) {
    transaction.show(target);
} else {
    transaction.addToBackStack(button_id + "stack_item");
    transaction.replace(R.id.page_fragment, target);
}
transaction.commit();

Inną rzeczą, o której należy pamiętać, jest to, że chociaż zachowuje to naturalny porządek dla samych fragmentów, nadal możesz potrzebować obsługiwać samą aktywność, która jest niszczona i odtwarzana po zmianie orientacji (konfiguracji). Aby obejść ten problem w AndroidManifest.xml dla swojego węzła:

android:configChanges="orientation|screenSize"

W Androidzie 3.0 i nowszych screenSizejest to najwyraźniej wymagane.

Powodzenia

rmirabelle
źródło
transaction.addToBackStack (button_id + "stack_item"); // do czego służy ta linia. Co to jest button_id tutaj?
raghu_3
button_id to tylko zmyślona zmienna. Argument ciągu przekazany do addToBackStack jest po prostu opcjonalną nazwą stanu backstack - możesz ustawić go na null, jeśli zarządzasz tylko jednym backstackiem.
rmirabelle
, mam problem podobny do tego z fragmentami, czy mógłbyś się temu przyjrzeć- stackoverflow.com/questions/22468977/…
raghu_3
1
Nigdy nie dodawaj android:configChanges=everythinYouCanThinkOf|moreThingsYouFoundOnTheInternetsw swoim manifeście. Zamiast tego dowiedz się, jak zapisywać i przywracać stan, na przykład stąd: speakerdeck.com/cyrilmottier/…
Marcin Koziński
A kiedy już dowiesz się, jak niesamowicie nieporęczny jest stan oszczędzania i że przedstawione rozwiązanie rozwiązuje problem najdokładniej w twojej konkretnej sytuacji, śmiało i używaj go, bez obawy o negatywne głosy od tych, którzy się nie zgadzają ;-)
rmirabelle
5

Najlepsze rozwiązanie, które znalazłem, znajduje się poniżej:

onSavedInstanceState (): zawsze wywoływana wewnątrz fragmentu, gdy aktywność ma zostać zamknięta (Przenieś aktywność z jednej do drugiej lub zmiany konfiguracji). Więc jeśli wywołujemy wiele fragmentów tej samej aktywności, musimy zastosować następujące podejście:

Użyj OnDestroyView () fragmentu i zapisz cały obiekt wewnątrz tej metody. Następnie OnActivityCreated (): sprawdź, czy obiekt ma wartość null, czy nie (ponieważ ta metoda wywołuje za każdym razem). Teraz przywróć stan obiektu tutaj.

Zawsze działa!

Aman Goel
źródło
4

jeśli obsługujesz zmiany konfiguracji w aktywności fragmentu określonej w manifeście Androida w ten sposób

<activity
    android:name=".courses.posts.EditPostActivity"
    android:configChanges="keyboardHidden|orientation"
    android:screenOrientation="unspecified" />

wtedy onSaveInstanceStatefragment nie zostanie wywołany, a savedInstanceStateobiekt zawsze będzie pusty.

Brook Oldre
źródło
1

nie uważam, że onSaveInstanceStateto dobre rozwiązanie. po prostu używa do działalności, która została zniszczona.

Od Androida 3.0 Fragmenem zarządza FragmentManager, warunek jest następujący: jedna czynność mapująca wiele fragmentów, gdy fragment zostanie dodany (nie zastąpi: zostanie odtworzony) w backStack, widok zostanie zniszczony. gdy wrócisz do ostatniego, wyświetli się jak poprzednio.

Myślę więc, że fragmentManger i transakcja są wystarczająco dobre, aby sobie z tym poradzić.

smallfatter
źródło
0

Użyłem podejścia hybrydowego do fragmentów zawierających widok listy. Wydaje się, że działa wydajnie, ponieważ nie zastępuję obecnego fragmentu, a raczej dodaję nowy fragment i ukrywam obecny. W działaniu, w którym znajdują się moje fragmenty, mam następującą metodę:

public void addFragment(Fragment currentFragment, Fragment targetFragment, String tag) {
    FragmentManager fragmentManager = getSupportFragmentManager();
    FragmentTransaction transaction = fragmentManager.beginTransaction();
    transaction.setCustomAnimations(0,0,0,0);
    transaction.hide(currentFragment);
    // use a fragment tag, so that later on we can find the currently displayed fragment
    transaction.add(R.id.frame_layout, targetFragment, tag)
            .addToBackStack(tag)
            .commit();
}

Używam tej metody w moim fragmencie (zawierającym widok listy) za każdym razem, gdy element listy jest klikany / stuknięty (i dlatego muszę uruchomić / wyświetlić fragment szczegółów):

FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
SearchFragment currentFragment = (SearchFragment) fragmentManager.findFragmentByTag(getFragmentTags()[0]);
DetailsFragment detailsFragment = DetailsFragment.newInstance("some object containing some details");
((MainActivity) getActivity()).addFragment(currentFragment, detailsFragment, "Details");

getFragmentTags()zwraca tablicę ciągów, których używam jako znaczników dla różnych fragmentów, kiedy dodam nowy fragment (patrz transaction.addmetoda w addFragmentmetodzie powyżej).

We fragmencie zawierającym widok listy robię to w jego metodzie onPause ():

@Override
public void onPause() {
    // keep the list view's state in memory ("save" it) 
    // before adding a new fragment or replacing current fragment with a new one
    ListView lv =  (ListView) getActivity().findViewById(R.id.listView);
    mListViewState = lv.onSaveInstanceState();
    super.onPause();
}

Następnie w onCreateView fragmentu (właściwie w metodzie, która jest wywoływana w onCreateView) przywracam stan:

// Restore previous state (including selected item index and scroll position)
if(mListViewState != null) {
    Log.d(TAG, "Restoring the listview's state.");
    lv.onRestoreInstanceState(mListViewState);
}
Javad Sadeqzadeh
źródło
0

W końcu po wypróbowaniu wielu z tych skomplikowanych rozwiązań, ponieważ potrzebowałem tylko zapisać / przywrócić pojedynczą wartość w moim fragmencie (zawartość EditText) i chociaż może to nie być najbardziej eleganckie rozwiązanie, utworzenie SharedPreference i przechowywanie mojego stanu tam pracował dla mnie

VMMF
źródło
0

Prosty sposób na przechowywanie wartości pól w różnych fragmentach w działaniu

Utwórz instancje fragmentów i dodaj zamiast zamieniać i usuwać

    FragA  fa= new FragA();
    FragB  fb= new FragB();
    FragC  fc= new FragB();
    fragmentManager = getSupportFragmentManager();
    fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.add(R.id.fragmnt_container, fa);
    fragmentTransaction.add(R.id.fragmnt_container, fb);
    fragmentTransaction.add(R.id.fragmnt_container, fc);
    fragmentTransaction.show(fa);
    fragmentTransaction.hide(fb);
    fragmentTransaction.hide(fc);
    fragmentTransaction.commit();

Następnie po prostu pokaż i ukryj fragmenty, zamiast dodawać je i usuwać ponownie

    fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.hide(fa);
    fragmentTransaction.show(fb);
    fragmentTransaction.hide(fc);
    fragmentTransaction.commit()

;

Sreejesh K Nair
źródło
-1
private ViewPager viewPager;
viewPager = (ViewPager) findViewById(R.id.pager);
mAdapter = new TabsPagerAdapter(getSupportFragmentManager());
viewPager.setAdapter(mAdapter);
viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {

        @Override
        public void onPageSelected(int position) {
            // on changing the page
            // make respected tab selected
            actionBar.setSelectedNavigationItem(position);
        }

        @Override
        public void onPageScrolled(int arg0, float arg1, int arg2) {
        }

        @Override
        public void onPageScrollStateChanged(int arg0) {
        }
    });
}

@Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
}

@Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
    // on tab selected
    // show respected fragment view
    viewPager.setCurrentItem(tab.getPosition());
}

@Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
}
Sathish Kumar
źródło
5
Zastanów się nad dołączeniem niektórych informacji o swojej odpowiedzi, zamiast po prostu opublikować kod. Staramy się dostarczać nie tylko „poprawki”, ale pomagamy ludziom się uczyć. Powinieneś wyjaśnić, co było nie tak w oryginalnym kodzie, co zrobiłeś inaczej i dlaczego zmiany zadziałały.
Andrew Barber