ViewPager i fragmenty - jaki jest właściwy sposób przechowywania stanu fragmentu?

492

Fragmenty wydają się bardzo przydatne do rozdzielenia logiki interfejsu użytkownika na niektóre moduły. Ale wraz z ViewPagercyklem życia nadal jest dla mnie mglisty. Więc myśli Guru są bardzo potrzebne!

Edytować

Zobacz głupie rozwiązanie poniżej ;-)

Zakres

Główna działalność ma ViewPagerfragmentami. Fragmenty te mogą implementować nieco inną logikę dla innych (podrzędnych) działań, więc dane fragmentów są wypełniane przez interfejs wywołania zwrotnego wewnątrz działania. I wszystko działa dobrze przy pierwszym uruchomieniu, ale! ...

Problem

Kiedy aktywność zostanie odtworzona (np. Przy zmianie orientacji), wykonaj również ViewPagerfragmenty. Kod (znajdziesz poniżej) mówi, że za każdym razem, gdy działanie jest tworzone, próbuję utworzyć nowy ViewPageradapter fragmentów, taki sam jak fragmenty (może to jest problem), ale FragmentManager już ma wszystkie te fragmenty gdzieś (gdzie?) I uruchamia dla nich mechanizm rekreacji. Tak więc mechanizm odtwarzania wywołuje „stary” fragment onAttach, onCreateView itp. Z moim wywołaniem interfejsu zwrotnego do inicjowania danych za pomocą zaimplementowanej metody działania. Ale ta metoda wskazuje na nowo utworzony fragment, który jest tworzony za pomocą metody onCreate działania.

Kwestia

Może używam niewłaściwych wzorów, ale nawet książka na Androida 3 Pro nie ma o tym wiele. Więc proszę , daj mi jeden-dwa uderzenie i wskaż, jak to zrobić we właściwy sposób. Wielkie dzięki!

Kod

Główna aktywność

public class DashboardActivity extends BasePagerActivity implements OnMessageListActionListener {

private MessagesFragment mMessagesFragment;

@Override
protected void onCreate(Bundle savedInstanceState) {
    Logger.d("Dash onCreate");
    super.onCreate(savedInstanceState);

    setContentView(R.layout.viewpager_container);
    new DefaultToolbar(this);

    // create fragments to use
    mMessagesFragment = new MessagesFragment();
    mStreamsFragment = new StreamsFragment();

    // set titles and fragments for view pager
    Map<String, Fragment> screens = new LinkedHashMap<String, Fragment>();
    screens.put(getApplicationContext().getString(R.string.dashboard_title_dumb), new DumbFragment());
    screens.put(getApplicationContext().getString(R.string.dashboard_title_messages), mMessagesFragment);

    // instantiate view pager via adapter
    mPager = (ViewPager) findViewById(R.id.viewpager_pager);
    mPagerAdapter = new BasePagerAdapter(screens, getSupportFragmentManager());
    mPager.setAdapter(mPagerAdapter);

    // set title indicator
    TitlePageIndicator indicator = (TitlePageIndicator) findViewById(R.id.viewpager_titles);
    indicator.setViewPager(mPager, 1);

}

/* set of fragments callback interface implementations */

@Override
public void onMessageInitialisation() {

    Logger.d("Dash onMessageInitialisation");
    if (mMessagesFragment != null)
        mMessagesFragment.loadLastMessages();
}

@Override
public void onMessageSelected(Message selectedMessage) {

    Intent intent = new Intent(this, StreamActivity.class);
    intent.putExtra(Message.class.getName(), selectedMessage);
    startActivity(intent);
}

BasePagerActivity aka pomocnik

public class BasePagerActivity extends FragmentActivity {

BasePagerAdapter mPagerAdapter;
ViewPager mPager;
}

Adapter

public class BasePagerAdapter extends FragmentPagerAdapter implements TitleProvider {

private Map<String, Fragment> mScreens;

public BasePagerAdapter(Map<String, Fragment> screenMap, FragmentManager fm) {

    super(fm);
    this.mScreens = screenMap;
}

@Override
public Fragment getItem(int position) {

    return mScreens.values().toArray(new Fragment[mScreens.size()])[position];
}

@Override
public int getCount() {

    return mScreens.size();
}

@Override
public String getTitle(int position) {

    return mScreens.keySet().toArray(new String[mScreens.size()])[position];
}

// hack. we don't want to destroy our fragments and re-initiate them after
@Override
public void destroyItem(View container, int position, Object object) {

    // TODO Auto-generated method stub
}

}

Fragment

public class MessagesFragment extends ListFragment {

private boolean mIsLastMessages;

private List<Message> mMessagesList;
private MessageArrayAdapter mAdapter;

private LoadMessagesTask mLoadMessagesTask;
private OnMessageListActionListener mListener;

// define callback interface
public interface OnMessageListActionListener {
    public void onMessageInitialisation();
    public void onMessageSelected(Message selectedMessage);
}

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    // setting callback
    mListener = (OnMessageListActionListener) activity;
    mIsLastMessages = activity instanceof DashboardActivity;

}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    inflater.inflate(R.layout.fragment_listview, container);
    mProgressView = inflater.inflate(R.layout.listrow_progress, null);
    mEmptyView = inflater.inflate(R.layout.fragment_nodata, null);
    return super.onCreateView(inflater, container, savedInstanceState);
}

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);

    // instantiate loading task
    mLoadMessagesTask = new LoadMessagesTask();

    // instantiate list of messages
    mMessagesList = new ArrayList<Message>();
    mAdapter = new MessageArrayAdapter(getActivity(), mMessagesList);
    setListAdapter(mAdapter);
}

@Override
public void onResume() {
    mListener.onMessageInitialisation();
    super.onResume();
}

public void onListItemClick(ListView l, View v, int position, long id) {
    Message selectedMessage = (Message) getListAdapter().getItem(position);
    mListener.onMessageSelected(selectedMessage);
    super.onListItemClick(l, v, position, id);
}

/* public methods to load messages from host acitivity, etc... */
}

Rozwiązanie

Głupim rozwiązaniem jest zapisanie fragmentów wewnątrz onSaveInstanceState (działania hosta) za pomocą putFragment i umieszczenie ich w środku onCreate poprzez getFragment. Ale nadal mam dziwne przeczucie, że rzeczy nie powinny tak działać ... Zobacz poniższy kod:

    @Override
protected void onSaveInstanceState(Bundle outState) {

    super.onSaveInstanceState(outState);
    getSupportFragmentManager()
            .putFragment(outState, MessagesFragment.class.getName(), mMessagesFragment);
}

protected void onCreate(Bundle savedInstanceState) {
    Logger.d("Dash onCreate");
    super.onCreate(savedInstanceState);

    ...
    // create fragments to use
    if (savedInstanceState != null) {
        mMessagesFragment = (MessagesFragment) getSupportFragmentManager().getFragment(
                savedInstanceState, MessagesFragment.class.getName());
                StreamsFragment.class.getName());
    }
    if (mMessagesFragment == null)
        mMessagesFragment = new MessagesFragment();
    ...
}
Oleksii Malovanyi
źródło
1
Zastanawiam się teraz: czy powinienem zastosować inne podejście lub spróbować zapisać fragmenty głównej aktywności (Dashboard) za pośrednictwem onSavedInstancestate, aby użyć ich w onCreate (). Czy istnieje odpowiedni sposób na zapisanie tych fragmentów i pobranie ich z pakietu w programie onCreate? Nie wydają się być paczkami ...
Oleksii Malovanyi
2
Drugie podejście działa - patrz „Sulution”. Ale to wydaje się być brzydkim fragmentem kodu, prawda?
Oleksii Malovanyi
1
Czy z uwagi na wysiłki zmierzające do wyczyszczenia tagu Androida (szczegóły tutaj: meta.stackexchange.com/questions/100529/... ) czy mógłbyś opublikować swoje rozwiązanie jako odpowiedź i oznaczyć je jako wybrane? W ten sposób nie pojawi się jako pytanie bez odpowiedzi :)
Alexander Lucas
1
tak, myślę, że jest OK. Miałem nadzieję na coś lepszego niż moje ...
Oleksii Malovanyi
1
Czy głupie rozwiązanie w ogóle działa? Daje mi to wyjątek wskaźnika zerowego ..
Zhen Liu

Odpowiedzi:

451

Gdy FragmentPagerAdapterdodaje fragment do narzędzia FragmentManager, używa specjalnego znacznika na podstawie konkretnej pozycji, w której fragment zostanie umieszczony. FragmentPagerAdapter.getItem(int position)jest wywoływany tylko wtedy, gdy fragment dla tej pozycji nie istnieje. Po obróceniu Android zauważy, że już utworzył / zapisał fragment dla tej konkretnej pozycji, więc po prostu próbuje się z nim połączyć FragmentManager.findFragmentByTag(), zamiast tworzyć nową. Wszystko to jest bezpłatne przy użyciu FragmentPagerAdapteri dlatego zwykle w twojej getItem(int)metodzie znajduje się kod inicjalizacji fragmentu .

Nawet jeśli nie używamy FragmentPagerAdapter, nie jest dobrym pomysłem tworzenie nowego fragmentu za każdym razem Activity.onCreate(Bundle). Jak zauważyłeś, fragment dodany do narzędzia FragmentManager zostanie odtworzony po obróceniu i nie ma potrzeby dodawania go ponownie. Takie postępowanie jest częstą przyczyną błędów podczas pracy z fragmentami.

Typowe podejście podczas pracy z fragmentami jest następujące:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    ...

    CustomFragment fragment;
    if (savedInstanceState != null) {
        fragment = (CustomFragment) getSupportFragmentManager().findFragmentByTag("customtag");
    } else {
        fragment = new CustomFragment();
        getSupportFragmentManager().beginTransaction().add(R.id.container, fragment, "customtag").commit(); 
    }

    ...

}

Korzystając z a FragmentPagerAdapter, rezygnujemy z zarządzania fragmentami na adapterze i nie musimy wykonywać powyższych kroków. Domyślnie ładuje tylko jeden Fragment przed i za bieżącą pozycją (chociaż nie niszczy ich, chyba że używasz FragmentStatePagerAdapter). Jest to kontrolowane przez ViewPager.setOffscreenPageLimit (int) . Z tego powodu nie ma gwarancji, że bezpośrednie wywoływanie metod na fragmentach poza adapterem będzie poprawne, ponieważ mogą nawet nie być żywe.

Krótko mówiąc, twoje rozwiązanie, aby putFragmentmóc później uzyskać referencję, nie jest tak szalone i nie jest tak odmienne od zwykłego sposobu używania fragmentów (powyżej). W innym przypadku trudno jest uzyskać odwołanie, ponieważ fragment jest dodawany przez adapter, a nie osobiście. Po prostu upewnij się, że offscreenPageLimitjest wystarczająco wysoki, aby załadować pożądane fragmenty przez cały czas, ponieważ polegasz na jego obecności. To omija leniwe możliwości ładowania ViewPager, ale wydaje się, że to jest to, czego chcesz dla swojej aplikacji.

Innym podejściem jest zastąpienie FragmentPageAdapter.instantiateItem(View, int)i zapisanie odwołania do fragmentu zwróconego z super wywołania przed jego zwróceniem (logika polega na znalezieniu fragmentu, jeśli jest już obecny).

Aby uzyskać pełniejszy obraz, spójrz na niektóre źródła FragmentPagerAdapter (krótki) i ViewPager (długi).

antonyt
źródło
7
Uwielbiałem ostatnią część. Miałem pamięć podręczną dla fragmentów i przeniosłem logikę pamięci podręcznej do wewnątrz FragmentPageAdapter.instantiateItem(View, int). W końcu naprawiono długotrwały błąd, który pojawia się tylko w zmianie rotacji / konfiguracji i doprowadzał mnie do szału ...
Carlos Sobrinho
2
To rozwiązało problem dotyczący zmiany rotacji. Być może po prostu nie widzę, żeby szukać, ale czy jest to udokumentowane? tzn. używasz tagu, aby przywrócić poprzedni stan? To może oczywista odpowiedź, ale jestem całkiem nowy w rozwoju Androida.
1
@ Przemysłowy środek przeciwdepresyjny Jest to identyfikator kontenera (np. FrameLayout), do którego należy dodać Fragment.
antonyt
1
btw, dla mnie było FragmentPageAdapter.instantiateItem(ViewGroup, int)raczej niż FragmentPageAdapter.instantiateItem(View, int).
Nazwa wyświetlana
1
A właściwie czy wiesz, która (jeśli w ogóle) metoda cyklu życia fragmentu jest wywoływana, gdy fragment zostanie zrzucony z ekranu? Czy to jest onDetach()coś innego?
ygesher
36

Chcę zaoferować rozwiązanie, które rozszerza się na antonyt„s wspaniałą odpowiedź i wzmianki o przesłanianie FragmentPageAdapter.instantiateItem(View, int)zapisać odniesień do stworzył Fragmentswięc można zrobić pracę na nich później. To powinno również działać FragmentStatePagerAdapter; szczegóły patrz uwagi.


Oto prosty przykład, w jaki sposób uzyskać odwołanie do Fragmentszwracanego przez FragmentPagerAdapter, który nie opiera się na wewnętrznym tagszestawie w Fragments. Kluczem jest zastąpienie instantiateItem()i zapisanie tam referencji zamiast w getItem().

public class SomeActivity extends Activity {
    private FragmentA m1stFragment;
    private FragmentB m2ndFragment;

    // other code in your Activity...

    private class CustomPagerAdapter extends FragmentPagerAdapter {
        // other code in your custom FragmentPagerAdapter...

        public CustomPagerAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int position) {
            // Do NOT try to save references to the Fragments in getItem(),
            // because getItem() is not always called. If the Fragment
            // was already created then it will be retrieved from the FragmentManger
            // and not here (i.e. getItem() won't be called again).
            switch (position) {
                case 0:
                    return new FragmentA();
                case 1:
                    return new FragmentB();
                default:
                    // This should never happen. Always account for each position above
                    return null;
            }
        }

        // Here we can finally safely save a reference to the created
        // Fragment, no matter where it came from (either getItem() or
        // FragmentManger). Simply save the returned Fragment from
        // super.instantiateItem() into an appropriate reference depending
        // on the ViewPager position.
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
            // save the appropriate reference depending on position
            switch (position) {
                case 0:
                    m1stFragment = (FragmentA) createdFragment;
                    break;
                case 1:
                    m2ndFragment = (FragmentB) createdFragment;
                    break;
            }
            return createdFragment;
        }
    }

    public void someMethod() {
        // do work on the referenced Fragments, but first check if they
        // even exist yet, otherwise you'll get an NPE.

        if (m1stFragment != null) {
            // m1stFragment.doWork();
        }

        if (m2ndFragment != null) {
            // m2ndFragment.doSomeWorkToo();
        }
    }
}

lub jeśli wolisz pracować ze tagszmiennymi / odwołaniami do klasy Fragments, możesz również pobrać tagszestaw FragmentPagerAdapterw ten sam sposób: UWAGA: nie dotyczy to, FragmentStatePagerAdapterponieważ nie ustawia się tagspodczas jego tworzenia Fragments.

@Override
public Object instantiateItem(ViewGroup container, int position) {
    Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
    // get the tags set by FragmentPagerAdapter
    switch (position) {
        case 0:
            String firstTag = createdFragment.getTag();
            break;
        case 1:
            String secondTag = createdFragment.getTag();
            break;
    }
    // ... save the tags somewhere so you can reference them later
    return createdFragment;
}

Zauważ, że ta metoda NIE polega na naśladowaniu tagzestawu wewnętrznego FragmentPagerAdapteri zamiast tego używa odpowiednich interfejsów API do ich pobierania. W ten sposób nawet jeśli tagzmiany w przyszłych wersjach SupportLibrarynadal będą bezpieczne.


Nie zapominaj, że w zależności od projektu Activity, nad Fragmentsktórym próbujesz pracować, może jeszcze nie istnieć, więc musisz to uwzględnić, nullsprawdzając przed użyciem referencji.

Ponadto, jeśli zamiast tego pracujesz FragmentStatePagerAdapter, nie chcesz przechowywać twardych odniesień do siebie, Fragmentsponieważ możesz mieć wiele z nich, a twarde odniesienia niepotrzebnie utrzymują je w pamięci. Zamiast tego zapisz Fragmentodniesienia w WeakReferencezmiennych zamiast standardowych. Lubię to:

WeakReference<Fragment> m1stFragment = new WeakReference<Fragment>(createdFragment);
// ...and access them like so
Fragment firstFragment = m1stFragment.get();
if (firstFragment != null) {
    // reference hasn't been cleared yet; do work...
}
Tony Chan
źródło
w przypadku braku pamięci android może poprosić FragmentManager o zniszczenie nieużywanych fragmentów, prawda? Jeśli mam rację, twoja druga wersja będzie działać, pierwsza może się nie powieść. (Nie wiem, czy robią to obecne wersje Androida, ale wydaje się, że musimy się tego spodziewać.)
Moritz Oba
1
co z tworzeniem FragmetPagerAdapterw polu OnCreate of Activity przy każdym obrocie ekranu. Czy to źle, ponieważ może ominąć ponowne użycie już dodanych fragmentów wFragmentPagerAdapter
Bahman
Przesłonięcie instantiateItem()jest właściwą drogą; pomogło mi to obsłużyć obracanie ekranu i odzyskiwanie istniejących instancji Fragmentów po wznowieniu działania i adaptera; Zostawiłem sobie komentarze w kodzie jako przypomnienie: po rotacji getItem()NIE jest wywoływany; instantiateItem()wywoływana jest tylko ta metoda . Super implementacja instantiateItem()faktycznie dołącza fragmenty po rotacji (w razie potrzeby), zamiast tworzenia nowych instancji!
HelloImKevo,
Fragment createdFragment = (Fragment) super.instantiateItem..W pierwszym rozwiązaniu włączam wskaźnik zerowy .
AlexS,
Po przeszukaniu wszystkich różnych powtórzeń tego problemu, było to najlepsze rozwiązanie. (Odsyłacz do innego Q z bardziej odpowiednim tytułem, więc dziękuję za to!)
MandisaW
18

Znalazłem inne stosunkowo łatwe rozwiązanie dla twojego pytania.

Jak widać z kodu źródłowego FragmentPagerAdapter , fragmenty zarządzane przez FragmentPagerAdaptersklep w FragmentManagerpod tagiem wygenerowanym przy użyciu:

String tag="android:switcher:" + viewId + ":" + index;

To viewIdjest container.getId(), to containerjest twoja ViewPagerinstancja. Jest indexto pozycja fragmentu. Dlatego możesz zapisać identyfikator obiektu w outState:

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("viewpagerid" , mViewPager.getId() );
}

@Override
    protected void onCreate(Bundle savedInstanceState) {
    setContentView(R.layout.activity_main);
    if (savedInstanceState != null)
        viewpagerid=savedInstanceState.getInt("viewpagerid", -1 );  

    MyFragmentPagerAdapter titleAdapter = new MyFragmentPagerAdapter (getSupportFragmentManager() , this);        
    mViewPager = (ViewPager) findViewById(R.id.pager);
    if (viewpagerid != -1 ){
        mViewPager.setId(viewpagerid);
    }else{
        viewpagerid=mViewPager.getId();
    }
    mViewPager.setAdapter(titleAdapter);

Jeśli chcesz komunikować się z tym fragmentem, możesz uzyskać, jeśli FragmentManager:

getSupportFragmentManager().findFragmentByTag("android:switcher:" + viewpagerid + ":0")
użytkownik2110066
źródło
21
hmmm Nie sądzę, że jest to dobra droga, ponieważ odpowiadasz na wewnętrzną konwencję nazewnictwa tagów, która w żadnym wypadku nie pozostanie taka sama na zawsze.
Dori,
1
Dla tych, którzy szukają rozwiązania, które nie zależy od wewnętrznego tag, rozważ wypróbowanie mojej odpowiedzi .
Tony Chan,
16

Chcę zaoferować alternatywne rozwiązanie dla być może nieco innej sprawy, ponieważ wiele moich poszukiwań odpowiedzi prowadziło mnie do tego wątku.

Mój przypadek - dynamicznie tworzę / dodam strony i wsuwam je do ViewPagera, ale po obróceniu (onConfigurationChange) powstaje nowa strona, ponieważ oczywiście OnCreate jest wywoływany ponownie. Ale chcę zachować odniesienie do wszystkich stron, które zostały utworzone przed rotacją.

Problem - nie mam unikalnych identyfikatorów dla każdego tworzonego przeze mnie fragmentu, więc jedynym sposobem na odniesienie było przechowanie odnośników w macierzy w celu przywrócenia po zmianie rotacji / konfiguracji.

Obejście - Kluczowym założeniem było, aby Działanie (które wyświetla Fragmenty) również zarządzało tablicą odniesień do istniejących Fragmentów, ponieważ to działanie może wykorzystywać Pakiety w onSaveInstanceState

public class MainActivity extends FragmentActivity

Dlatego w ramach tego działania oświadczam, że członek prywatny śledzi otwarte strony

private List<Fragment> retainedPages = new ArrayList<Fragment>();

Jest aktualizowany za każdym razem, gdy onSaveInstanceState jest wywoływany i przywracany w onCreate

@Override
protected void onSaveInstanceState(Bundle outState) {
    retainedPages = _adapter.exportList();
    outState.putSerializable("retainedPages", (Serializable) retainedPages);
    super.onSaveInstanceState(outState);
}

... więc po zapisaniu można go odzyskać ...

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    if (savedInstanceState != null) {
        retainedPages = (List<Fragment>) savedInstanceState.getSerializable("retainedPages");
    }
    _mViewPager = (CustomViewPager) findViewById(R.id.viewPager);
    _adapter = new ViewPagerAdapter(getApplicationContext(), getSupportFragmentManager());
    if (retainedPages.size() > 0) {
        _adapter.importList(retainedPages);
    }
    _mViewPager.setAdapter(_adapter);
    _mViewPager.setCurrentItem(_adapter.getCount()-1);
}

Były to niezbędne zmiany w głównej działalności, dlatego potrzebowałem członków i metod w moim FragmentPagerAdapter, aby to działało, więc w

public class ViewPagerAdapter extends FragmentPagerAdapter

identyczny konstrukt (jak pokazano powyżej w MainActivity)

private List<Fragment> _pages = new ArrayList<Fragment>();

i ta synchronizacja (jak używana powyżej w onSaveInstanceState) jest obsługiwana w szczególności przez metody

public List<Fragment> exportList() {
    return _pages;
}

public void importList(List<Fragment> savedPages) {
    _pages = savedPages;
}

I wreszcie w klasie fragmentów

public class CustomFragment extends Fragment

aby wszystko działało, najpierw były dwie zmiany

public class CustomFragment extends Fragment implements Serializable

a następnie dodanie tego do onCreate, aby Fragmenty nie zostały zniszczone

setRetainInstance(true);

Nadal jestem w trakcie owijania się wokół Fragmentów i cyklu życia Androida, więc tutaj jest zastrzeżenie, że w tej metodzie mogą występować zwolnienia / nieefektywności. Ale to działa dla mnie i mam nadzieję, że mogą być pomocne dla innych w przypadkach podobnych do moich.

Frank Yin
źródło
2
+1 za setRetainInstance (true) - czarna magia, której szukałem!
deklinacja
Było to jeszcze łatwiejsze przy użyciu FragmentStatePagerAdapter (v13). Które rozwiązanie dla ciebie z przywracaniem i zwalnianiem stanu.
Rzeźnik
Jasny opis i bardzo fajne rozwiązanie. Dziękuję Ci!
Bruno Bieri
8

Moje rozwiązanie jest bardzo niegrzeczne, ale działa: ponieważ moje fragmenty są dynamicznie tworzone z zachowanych danych, po prostu usuwam wszystkie fragmenty z PageAdapterprzed wywołaniem, super.onSaveInstanceState()a następnie odtwarzam je po utworzeniu aktywności:

@Override
protected void onSaveInstanceState(Bundle outState) {
    outState.putInt("viewpagerpos", mViewPager.getCurrentItem() );
    mSectionsPagerAdapter.removeAllfragments();
    super.onSaveInstanceState(outState);
}

Nie możesz ich usunąć, w onDestroy()przeciwnym razie otrzymasz ten wyjątek:

java.lang.IllegalStateException: Nie można wykonać tej czynności po onSaveInstanceState

Tutaj kod w adapterze strony:

public void removeAllfragments()
{
    if ( mFragmentList != null ) {
        for ( Fragment fragment : mFragmentList ) {
            mFm.beginTransaction().remove(fragment).commit();
        }
        mFragmentList.clear();
        notifyDataSetChanged();
    }
}

Zapisuję tylko bieżącą stronę i przywracam ją onCreate()po utworzeniu fragmentów.

if (savedInstanceState != null)
    mViewPager.setCurrentItem( savedInstanceState.getInt("viewpagerpos", 0 ) );  
Sir John
źródło
Nie wiem dlaczego, ale powiadomienieDataSetChanged (); powoduje awarię aplikacji
Malachiasz
4

Co to jest BasePagerAdapter? Powinieneś użyć jednego ze standardowych adapterów pagera - albo FragmentPagerAdapteralbo FragmentStatePagerAdapter, zależnie od tego, czy chcesz Fragmenty, które nie są już potrzebneViewPager były trzymane w pobliżu (te pierwsze), czy też ich stan został zachowany (ten drugi) i ponownie utworzony, jeśli potrzebne ponownie.

Przykładowy kod do użycia ViewPagermożna znaleźć tutaj

Prawdą jest, że zarządzanie fragmentami w pagerach widoków między instancjami działania jest nieco skomplikowane, ponieważ FragmentManagerw ramach tej funkcji dba się o zachowanie stanu i przywracanie wszelkich aktywnych fragmentów, które utworzył pager. Wszystko to naprawdę oznacza, że ​​adapter podczas inicjowania musi upewnić się, że ponownie łączy się z wszelkimi odtworzonymi fragmentami. Możesz spojrzeć na kod FragmentPagerAdapterlub FragmentStatePagerAdapterzobaczyć, jak to się robi.

hackbod
źródło
1
Kod BasePagerAdapter jest dostępny w moim pytaniu. Jak widać, po prostu rozszerza FragmentPagerAdapter w celu implementacji TitleProvider . Więc wszystko już działa z Androidem sugerowanym przez deweloperów.
Oleksii Malovanyi
Nie wiedziałem o „FragmentStatePagerAdapter”. Uratowałeś mi dosłownie godziny. Dzięki.
Knossos,
2

Jeśli ktoś ma problemy ze swoim FragmentStatePagerAdapter niewłaściwie przywracającym stan jego fragmentów ... tzn. ... nowe Fragmenty są tworzone przez FragmentStatePagerAdapter zamiast przywracania ich ze stanu ...

Pamiętaj, aby zadzwonić ViewPager.setOffscreenPageLimit()ZANIM zadzwoniszViewPager.setAdapter(fragmentStatePagerAdapter)

Po zadzwonieniu ViewPager.setOffscreenPageLimit()... ViewPager to zrobi natychmiast spojrzy na adapter i spróbuje uzyskać jego fragmenty. Może się to zdarzyć, zanim ViewPager będzie mógł przywrócić Fragmenty z saveInstanceState (tworząc w ten sposób nowe Fragmenty, których nie można ponownie zainicjować z SavedInstanceState, ponieważ są nowe).

dell116
źródło
0

Wymyśliłem to proste i eleganckie rozwiązanie. Zakłada, że ​​aktywność jest odpowiedzialna za tworzenie Fragmentów, a Adapter po prostu je obsługuje.

To jest kod adaptera (tutaj nic dziwnego, z wyjątkiem faktu, że mFragmentsjest to lista fragmentów obsługiwanych przez działanie)

class MyFragmentPagerAdapter extends FragmentStatePagerAdapter {

    public MyFragmentPagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int position) {
        return mFragments.get(position);
    }

    @Override
    public int getCount() {
        return mFragments.size();
    }

    @Override
    public int getItemPosition(Object object) {
        return POSITION_NONE;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        TabFragment fragment = (TabFragment)mFragments.get(position);
        return fragment.getTitle();
    }
} 

Cały problem tego wątku polega na uzyskiwaniu odwołania do „starych” fragmentów, więc używam tego kodu w polu onCreate działania.

    if (savedInstanceState!=null) {
        if (getSupportFragmentManager().getFragments()!=null) {
            for (Fragment fragment : getSupportFragmentManager().getFragments()) {
                mFragments.add(fragment);
            }
        }
    }

Oczywiście w razie potrzeby możesz dodatkowo dostroić ten kod, na przykład upewniając się, że fragmenty są instancjami określonej klasy.

Merlevede
źródło
0

Aby uzyskać fragmenty po zmianie orientacji, musisz użyć .getTag ().

    getSupportFragmentManager().findFragmentByTag("android:switcher:" + viewPagerId + ":" + positionOfItemInViewPager)

Dla nieco większej obsługi napisałem własną ArrayList dla mojego PageAdapter, aby uzyskać fragment przez viewPagerId i FragmentClass w dowolnej pozycji:

public class MyPageAdapter extends FragmentPagerAdapter implements Serializable {
private final String logTAG = MyPageAdapter.class.getName() + ".";

private ArrayList<MyPageBuilder> fragmentPages;

public MyPageAdapter(FragmentManager fm, ArrayList<MyPageBuilder> fragments) {
    super(fm);
    fragmentPages = fragments;
}

@Override
public Fragment getItem(int position) {
    return this.fragmentPages.get(position).getFragment();
}

@Override
public CharSequence getPageTitle(int position) {
    return this.fragmentPages.get(position).getPageTitle();
}

@Override
public int getCount() {
    return this.fragmentPages.size();
}


public int getItemPosition(Object object) {
    //benötigt, damit bei notifyDataSetChanged alle Fragemnts refrehsed werden

    Log.d(logTAG, object.getClass().getName());
    return POSITION_NONE;
}

public Fragment getFragment(int position) {
    return getItem(position);
}

public String getTag(int position, int viewPagerId) {
    //getSupportFragmentManager().findFragmentByTag("android:switcher:" + R.id.shares_detail_activity_viewpager + ":" + myViewPager.getCurrentItem())

    return "android:switcher:" + viewPagerId + ":" + position;
}

public MyPageBuilder getPageBuilder(String pageTitle, int icon, int selectedIcon, Fragment frag) {
    return new MyPageBuilder(pageTitle, icon, selectedIcon, frag);
}


public static class MyPageBuilder {

    private Fragment fragment;

    public Fragment getFragment() {
        return fragment;
    }

    public void setFragment(Fragment fragment) {
        this.fragment = fragment;
    }

    private String pageTitle;

    public String getPageTitle() {
        return pageTitle;
    }

    public void setPageTitle(String pageTitle) {
        this.pageTitle = pageTitle;
    }

    private int icon;

    public int getIconUnselected() {
        return icon;
    }

    public void setIconUnselected(int iconUnselected) {
        this.icon = iconUnselected;
    }

    private int iconSelected;

    public int getIconSelected() {
        return iconSelected;
    }

    public void setIconSelected(int iconSelected) {
        this.iconSelected = iconSelected;
    }

    public MyPageBuilder(String pageTitle, int icon, int selectedIcon, Fragment frag) {
        this.pageTitle = pageTitle;
        this.icon = icon;
        this.iconSelected = selectedIcon;
        this.fragment = frag;
    }
}

public static class MyPageArrayList extends ArrayList<MyPageBuilder> {
    private final String logTAG = MyPageArrayList.class.getName() + ".";

    public MyPageBuilder get(Class cls) {
        // Fragment über FragmentClass holen
        for (MyPageBuilder item : this) {
            if (item.fragment.getClass().getName().equalsIgnoreCase(cls.getName())) {
                return super.get(indexOf(item));
            }
        }
        return null;
    }

    public String getTag(int viewPagerId, Class cls) {
        // Tag des Fragment unabhängig vom State z.B. nach bei Orientation change
        for (MyPageBuilder item : this) {
            if (item.fragment.getClass().getName().equalsIgnoreCase(cls.getName())) {
                return "android:switcher:" + viewPagerId + ":" + indexOf(item);
            }
        }
        return null;
    }
}

Więc po prostu stwórz MyPageArrayList z fragmentami:

    myFragPages = new MyPageAdapter.MyPageArrayList();

    myFragPages.add(new MyPageAdapter.MyPageBuilder(
            getString(R.string.widget_config_data_frag),
            R.drawable.ic_sd_storage_24dp,
            R.drawable.ic_sd_storage_selected_24dp,
            new WidgetDataFrag()));

    myFragPages.add(new MyPageAdapter.MyPageBuilder(
            getString(R.string.widget_config_color_frag),
            R.drawable.ic_color_24dp,
            R.drawable.ic_color_selected_24dp,
            new WidgetColorFrag()));

    myFragPages.add(new MyPageAdapter.MyPageBuilder(
            getString(R.string.widget_config_textsize_frag),
            R.drawable.ic_settings_widget_24dp,
            R.drawable.ic_settings_selected_24dp,
            new WidgetTextSizeFrag()));

i dodaj je do viewPager:

    mAdapter = new MyPageAdapter(getSupportFragmentManager(), myFragPages);
    myViewPager.setAdapter(mAdapter);

po tym można uzyskać po orientacji zmienić poprawny fragment, używając jego klasy:

        WidgetDataFrag dataFragment = (WidgetDataFrag) getSupportFragmentManager()
            .findFragmentByTag(myFragPages.getTag(myViewPager.getId(), WidgetDataFrag.class));
Złodziejaszek
źródło
-30

Dodaj:

   @SuppressLint("ValidFragment")

przed twoją klasą.

to nie działa, wykonaj coś takiego:

@SuppressLint({ "ValidFragment", "HandlerLeak" })
andro_stackoverflow
źródło