Fragmenty wydają się bardzo przydatne do rozdzielenia logiki interfejsu użytkownika na niektóre moduły. Ale wraz z ViewPager
cyklem ż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 ViewPager
fragmentami. 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ż ViewPager
fragmenty. Kod (znajdziesz poniżej) mówi, że za każdym razem, gdy działanie jest tworzone, próbuję utworzyć nowy ViewPager
adapter 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();
...
}
źródło
Odpowiedzi:
Gdy
FragmentPagerAdapter
dodaje 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życiuFragmentPagerAdapter
i dlatego zwykle w twojejgetItem(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 razemActivity.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:
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żywaszFragmentStatePagerAdapter
). 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
putFragment
mó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ę, żeoffscreenPageLimit
jest 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).
źródło
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 ...FragmentPageAdapter.instantiateItem(ViewGroup, int)
raczej niżFragmentPageAdapter.instantiateItem(View, int)
.onDetach()
coś innego?Chcę zaoferować rozwiązanie, które rozszerza się na
antonyt
„s wspaniałą odpowiedź i wzmianki o przesłanianieFragmentPageAdapter.instantiateItem(View, int)
zapisać odniesień do stworzyłFragments
wię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
Fragments
zwracanego przezFragmentPagerAdapter
, który nie opiera się na wewnętrznymtags
zestawie wFragments
. Kluczem jest zastąpienieinstantiateItem()
i zapisanie tam referencji zamiast wgetItem()
.lub jeśli wolisz pracować ze
tags
zmiennymi / odwołaniami do klasyFragments
, możesz również pobraćtags
zestawFragmentPagerAdapter
w ten sam sposób: UWAGA: nie dotyczy to,FragmentStatePagerAdapter
ponieważ nie ustawia siętags
podczas jego tworzeniaFragments
.Zauważ, że ta metoda NIE polega na naśladowaniu
tag
zestawu wewnętrznegoFragmentPagerAdapter
i zamiast tego używa odpowiednich interfejsów API do ich pobierania. W ten sposób nawet jeślitag
zmiany w przyszłych wersjachSupportLibrary
nadal będą bezpieczne.Nie zapominaj, że w zależności od projektu
Activity
, nadFragments
którym próbujesz pracować, może jeszcze nie istnieć, więc musisz to uwzględnić,null
sprawdzając przed użyciem referencji.Ponadto, jeśli zamiast tego pracujesz
FragmentStatePagerAdapter
, nie chcesz przechowywać twardych odniesień do siebie,Fragments
ponieważ możesz mieć wiele z nich, a twarde odniesienia niepotrzebnie utrzymują je w pamięci. Zamiast tego zapiszFragment
odniesienia wWeakReference
zmiennych zamiast standardowych. Lubię to:źródło
FragmetPagerAdapter
w polu OnCreate of Activity przy każdym obrocie ekranu. Czy to źle, ponieważ może ominąć ponowne użycie już dodanych fragmentów wFragmentPagerAdapter
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 rotacjigetItem()
NIE jest wywoływany;instantiateItem()
wywoływana jest tylko ta metoda . Super implementacjainstantiateItem()
faktycznie dołącza fragmenty po rotacji (w razie potrzeby), zamiast tworzenia nowych instancji!Fragment createdFragment = (Fragment) super.instantiateItem..
W pierwszym rozwiązaniu włączam wskaźnik zerowy .Znalazłem inne stosunkowo łatwe rozwiązanie dla twojego pytania.
Jak widać z kodu źródłowego FragmentPagerAdapter , fragmenty zarządzane przez
FragmentPagerAdapter
sklep wFragmentManager
pod tagiem wygenerowanym przy użyciu:To
viewId
jestcontainer.getId()
, tocontainer
jest twojaViewPager
instancja. Jestindex
to pozycja fragmentu. Dlatego możesz zapisać identyfikator obiektu woutState
:Jeśli chcesz komunikować się z tym fragmentem, możesz uzyskać, jeśli
FragmentManager
:źródło
tag
, rozważ wypróbowanie mojej odpowiedzi .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
Dlatego w ramach tego działania oświadczam, że członek prywatny śledzi otwarte strony
Jest aktualizowany za każdym razem, gdy onSaveInstanceState jest wywoływany i przywracany w onCreate
... więc po zapisaniu można go odzyskać ...
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
identyczny konstrukt (jak pokazano powyżej w MainActivity)
i ta synchronizacja (jak używana powyżej w onSaveInstanceState) jest obsługiwana w szczególności przez metody
I wreszcie w klasie fragmentów
aby wszystko działało, najpierw były dwie zmiany
a następnie dodanie tego do onCreate, aby Fragmenty nie zostały zniszczone
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.
źródło
Moje rozwiązanie jest bardzo niegrzeczne, ale działa: ponieważ moje fragmenty są dynamicznie tworzone z zachowanych danych, po prostu usuwam wszystkie fragmenty z
PageAdapter
przed wywołaniem,super.onSaveInstanceState()
a następnie odtwarzam je po utworzeniu aktywności:Nie możesz ich usunąć, w
onDestroy()
przeciwnym razie otrzymasz ten wyjątek:java.lang.IllegalStateException:
Nie można wykonać tej czynności poonSaveInstanceState
Tutaj kod w adapterze strony:
Zapisuję tylko bieżącą stronę i przywracam ją
onCreate()
po utworzeniu fragmentów.źródło
Co to jest
BasePagerAdapter
? Powinieneś użyć jednego ze standardowych adapterów pagera - alboFragmentPagerAdapter
alboFragmentStatePagerAdapter
, 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
ViewPager
można znaleźć tutajPrawdą jest, że zarządzanie fragmentami w pagerach widoków między instancjami działania jest nieco skomplikowane, ponieważ
FragmentManager
w 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 kodFragmentPagerAdapter
lubFragmentStatePagerAdapter
zobaczyć, jak to się robi.źródło
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).źródło
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
mFragments
jest to lista fragmentów obsługiwanych przez działanie)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.
Oczywiście w razie potrzeby możesz dodatkowo dostroić ten kod, na przykład upewniając się, że fragmenty są instancjami określonej klasy.
źródło
Aby uzyskać fragmenty po zmianie orientacji, musisz użyć .getTag ().
Dla nieco większej obsługi napisałem własną ArrayList dla mojego PageAdapter, aby uzyskać fragment przez viewPagerId i FragmentClass w dowolnej pozycji:
Więc po prostu stwórz MyPageArrayList z fragmentami:
i dodaj je do viewPager:
po tym można uzyskać po orientacji zmienić poprawny fragment, używając jego klasy:
źródło
Dodaj:
przed twoją klasą.
to nie działa, wykonaj coś takiego:
źródło