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ć?

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.


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.


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


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

}


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


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!


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.


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ć.


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

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


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

;


-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) {
}

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.