NAJNOWSZE INFORMACJE:
zawęziłem swój problem do problemu, w którym fragmentManager zachowuje wystąpienia starych fragmentów i mój viewpager nie jest zsynchronizowany z moim FragmentManager. Zobacz ten problem ... http://code.google.com/p/android/issues/detail?id=19211#makechanges . Nadal nie mam pojęcia, jak to rozwiązać. Jakieś sugestie...
Próbowałem to debugować przez długi czas i każda pomoc byłaby bardzo wdzięczna. Używam FragmentPagerAdapter, który akceptuje listę fragmentów, takich jak:
List<Fragment> fragments = new Vector<Fragment>();
fragments.add(Fragment.instantiate(this, Fragment1.class.getName()));
...
new PagerAdapter(getSupportFragmentManager(), fragments);
Wdrożenie jest standardowe. Używam biblioteki obliczalności ActionBarSherlock i v4 dla fragmentów.
Mój problem polega na tym, że po opuszczeniu aplikacji i otwarciu kilku innych aplikacji i powrocie fragmenty tracą odniesienie z powrotem do FragmentActivity (tj. getActivity() == null
). Nie wiem, dlaczego tak się dzieje. Próbowałem ustawić ręcznie, setRetainInstance(true);
ale to nie pomaga. Pomyślałem, że dzieje się tak, gdy moja FragmentActivity zostanie zniszczona, jednak dzieje się tak nadal, jeśli otworzę aplikację, zanim otrzymam komunikat dziennika. Czy są jakieś pomysły?
@Override
protected void onDestroy(){
Log.w(TAG, "DESTROYDESTROYDESTROYDESTROYDESTROYDESTROYDESTROY");
super.onDestroy();
}
Adapter:
public class PagerAdapter extends FragmentPagerAdapter {
private List<Fragment> fragments;
public PagerAdapter(FragmentManager fm, List<Fragment> fragments) {
super(fm);
this.fragments = fragments;
}
@Override
public Fragment getItem(int position) {
return this.fragments.get(position);
}
@Override
public int getCount() {
return this.fragments.size();
}
}
Jeden z moich fragmentów został usunięty, ale rozebrałem wszystko, co zostało rozebrane i nadal nie działa ...
public class MyFragment extends Fragment implements MyFragmentInterface, OnScrollListener {
...
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
handler = new Handler();
setHasOptionsMenu(true);
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
Log.w(TAG,"ATTACHATTACHATTACHATTACHATTACH");
context = activity;
if(context== null){
Log.e("IS NULL", "NULLNULLNULLNULLNULLNULLNULLNULLNULLNULLNULL");
}else{
Log.d("IS NOT NULL", "NOTNOTNOTNOTNOTNOTNOTNOT");
}
}
@Override
public void onActivityCreated(Bundle savedState) {
super.onActivityCreated(savedState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.my_fragment,container, false);
return v;
}
@Override
public void onResume(){
super.onResume();
}
private void callService(){
// do not call another service is already running
if(startLoad || !canSet) return;
// set flag
startLoad = true;
canSet = false;
// show the bottom spinner
addFooter();
Intent intent = new Intent(context, MyService.class);
intent.putExtra(MyService.STATUS_RECEIVER, resultReceiver);
context.startService(intent);
}
private ResultReceiver resultReceiver = new ResultReceiver(null) {
@Override
protected void onReceiveResult(int resultCode, final Bundle resultData) {
boolean isSet = false;
if(resultData!=null)
if(resultData.containsKey(MyService.STATUS_FINISHED_GET)){
if(resultData.getBoolean(MyService.STATUS_FINISHED_GET)){
removeFooter();
startLoad = false;
isSet = true;
}
}
switch(resultCode){
case MyService.STATUS_FINISHED:
stopSpinning();
break;
case SyncService.STATUS_RUNNING:
break;
case SyncService.STATUS_ERROR:
break;
}
}
};
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
menu.clear();
inflater.inflate(R.menu.activity, menu);
}
@Override
public void onPause(){
super.onPause();
}
public void onScroll(AbsListView arg0, int firstVisible, int visibleCount, int totalCount) {
boolean loadMore = /* maybe add a padding */
firstVisible + visibleCount >= totalCount;
boolean away = firstVisible+ visibleCount <= totalCount - visibleCount;
if(away){
// startLoad can now be set again
canSet = true;
}
if(loadMore)
}
public void onScrollStateChanged(AbsListView arg0, int state) {
switch(state){
case OnScrollListener.SCROLL_STATE_FLING:
adapter.setLoad(false);
lastState = OnScrollListener.SCROLL_STATE_FLING;
break;
case OnScrollListener.SCROLL_STATE_IDLE:
adapter.setLoad(true);
if(lastState == SCROLL_STATE_FLING){
// load the images on screen
}
lastState = OnScrollListener.SCROLL_STATE_IDLE;
break;
case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
adapter.setLoad(true);
if(lastState == SCROLL_STATE_FLING){
// load the images on screen
}
lastState = OnScrollListener.SCROLL_STATE_TOUCH_SCROLL;
break;
}
}
@Override
public void onDetach(){
super.onDetach();
if(this.adapter!=null)
this.adapter.clearContext();
Log.w(TAG, "DETACHEDDETACHEDDETACHEDDETACHEDDETACHEDDETACHED");
}
public void update(final int id, String name) {
if(name!=null){
getActivity().getSupportActionBar().setTitle(name);
}
}
}
Metoda update jest wywoływana, gdy użytkownik wchodzi w interakcję z innym fragmentem, a getActivity zwraca wartość null. Oto metoda, którą wywołuje drugi fragment ...
((MyFragment) pagerAdapter.getItem(1)).update(id, name);
Uważam, że gdy aplikacja zostanie zniszczona, a następnie utworzona ponownie, zamiast po prostu uruchamiać aplikację do domyślnego fragmentu, aplikacja uruchamia się, a następnie przeglądarka przechodzi do ostatniej znanej strony. Wydaje się to dziwne, czy aplikacja nie powinna po prostu załadować się do domyślnego fragmentu?
źródło
Odpowiedzi:
PagerAdapter.getItem
Występuje problem, ponieważ tworzysz instancje i przechowujesz odniesienia do swoich fragmentów poza programem i próbujesz ich używać niezależnie od ViewPagera. Jak mówi Seraph, masz gwarancje, że fragment został utworzony / dodany w ViewPager w określonym czasie - należy to uznać za szczegół implementacji. ViewPager leniwie ładuje swoje strony; domyślnie ładuje tylko bieżącą stronę oraz tę po lewej i po prawej stronie.Jeśli umieścisz aplikację w tle, fragmenty, które zostały dodane do menedżera fragmentów, zostaną automatycznie zapisane. Nawet jeśli Twoja aplikacja zostanie zabita, te informacje zostaną przywrócone po ponownym uruchomieniu aplikacji.
Teraz pomyśl, że obejrzałeś kilka stron, fragmenty A, B i C. Wiesz, że zostały one dodane do menedżera fragmentów. Ponieważ używasz,
FragmentPagerAdapter
a nieFragmentStatePagerAdapter
, te fragmenty będą nadal dodawane (ale potencjalnie odłączane) podczas przewijania do innych stron.Weź pod uwagę, że następnie uruchomisz swoją aplikację w tle, a następnie zostanie ona zabita. Kiedy wrócisz, Android zapamięta, że kiedyś miałeś fragmenty A, B i C w menedżerze fragmentów, więc odtwarza je dla Ciebie, a następnie dodaje. Jednak te, które są teraz dodane do menedżera fragmentów, NIE są tymi, które masz na liście fragmentów w swojej aktywności.
FragmentPagerAdapter nie podejmie próby wywołania,
getPosition
jeśli istnieje już fragment dodany dla tej konkretnej pozycji na stronie. W rzeczywistości, ponieważ fragment odtworzony przez Androida nigdy nie zostanie usunięty, nie masz nadziei na zastąpienie go wezwaniem dogetPosition
. Uzyskanie do niego uchwytu jest również dość trudne, aby uzyskać do niego odniesienie, ponieważ został dodany z nieznanym ci tagiem. Jest to zgodne z projektem; odradza się manipulowanie fragmentami, którymi zarządza pager widoku. Powinieneś wykonywać wszystkie swoje działania w obrębie fragmentu, komunikować się z działaniem i w razie potrzeby prosić o przejście do określonej strony.Wróćmy teraz do problemu z brakującą aktywnością. Wywołanie
pagerAdapter.getItem(1)).update(id, name)
po tym wszystkim powoduje zwrócenie fragmentu z listy, który nie został jeszcze dodany do menedżera fragmentów , a więc nie będzie miał odniesienia do działania. Sugerowałbym, że twoja metoda aktualizacji powinna zmodyfikować jakąś udostępnioną strukturę danych (prawdopodobnie zarządzaną przez działanie), a następnie, kiedy przejdziesz do określonej strony, może ona sama rysować się na podstawie tych zaktualizowanych danych.źródło
instantiateItem
i faktycznie powinieneś to zrobić w ramachonCreate
swojej działalności. zobacz szczegóły tutaj: stackoverflow.com/questions/14035090/…Znalazłem proste rozwiązanie, które zadziałało.
Spraw, aby adapter fragmentu rozszerzał FragmentStatePagerAdapter zamiast FragmentPagerAdapter i przesłonił metodę onSave, aby zwrócić wartość null
Zapobiega to odtworzeniu fragmentu przez Androida
Dzień później znalazłem inne, lepsze rozwiązanie.
Wezwij
setRetainInstance(true)
wszystkie swoje fragmenty i zapisz gdzieś odniesienia do nich. Zrobiłem to w zmiennej statycznej w mojej aktywności, ponieważ jest zadeklarowana jako singleTask, a fragmenty mogą pozostać takie same przez cały czas.W ten sposób android nie odtwarza fragmentów, ale używa tych samych instancji.
źródło
setRetainInstance(true)
zFragmentPagerAdapter
. Wszystko działa dobrze. Ale kiedy obracam urządzenie, adapter nadal ma fragmenty, ale fragmenty nie są wyświetlane. Metody cyklu życia fragmentów również nie są nazywane. Czy ktoś może pomóc?Rozwiązałem ten problem, uzyskując dostęp do moich fragmentów bezpośrednio przez FragmentManager zamiast za pośrednictwem FragmentPagerAdapter w ten sposób. Najpierw muszę znaleźć tag fragmentu wygenerowanego automatycznie przez FragmentPagerAdapter ...
Wtedy po prostu dostaję odniesienie do tego fragmentu i robię to, czego potrzebuję ...
Wewnątrz moich fragmentów ustawiłem
setRetainInstance(false);
tak, żebym mógł ręcznie dodawać wartości do pakietu saveInstanceState.a następnie w OnCreate chwytam ten klucz i przywracam stan fragmentu w razie potrzeby. Proste rozwiązanie, które było trudne (przynajmniej dla mnie) do wymyślenia.
źródło
FragmentByTag
w ViewPager.Globalne, sprawdzone rozwiązanie.
getSupportFragmentManager()
czasami zachowuje zerowe odniesienie, a przeglądarka stron nie tworzy nowego, ponieważ znajduje odniesienie do tego samego fragmentu. Aby przezwyciężyć to użycie,getChildFragmentManager()
rozwiązuje problem w prosty sposób.Nie rób tego:
new PagerAdapter(getSupportFragmentManager(), fragments);
Zrób to:
new PagerAdapter(getChildFragmentManager() , fragments);
źródło
FragmentStatePagerAdapter(activity!!.supportFragmentManager)
się łatwiejsza do oglądaniaFragmentStatePagerAdapter(childFragmentManager)
:)Nie próbuj wchodzić w interakcje między fragmentami w ViewPager. Nie możesz zagwarantować, że inny fragment jest dołączony lub nawet istnieje. Zamiast zmieniać tytuł paska akcji z fragmentu, możesz to zrobić z poziomu swojej aktywności. Użyj do tego standardowego wzorca interfejsu:
źródło
Możesz usunąć fragmenty, gdy niszczę przeglądarkę, w moim przypadku usunąłem je
onDestroyView()
z mojego fragmentu:źródło
ViewPager
opierał się również nachildFragmentManager
adapterze (niefragmentManager
). Działa również inny wariant: nie używajonDestroyView
, ale usuń fragmenty potomne przedViewPager
utworzeniem adaptera.Po kilku godzinach szukania podobnego problemu myślę, że mam inne rozwiązanie. Ten przynajmniej zadziałał dla mnie i muszę zmienić tylko kilka linii.
To jest problem, który miałem, mam działanie z pagerem widoku, który używa FragmentStatePagerAdapter z dwoma fragmentami. Wszystko działa dobrze, dopóki nie wymuszę zniszczenia działania (opcje programistyczne) lub nie obrócę ekranu. Zachowuję odniesienie do dwóch fragmentów po ich utworzeniu w metodzie getItem.
W tym momencie działanie zostanie utworzone ponownie i na tym etapie wszystko działa dobrze, ale straciłem odniesienie do moich fragmentów, ponieważ getItem nie jest ponownie wywoływane.
Oto jak rozwiązałem ten problem wewnątrz FragmentStatePagerAdapter:
Nie otrzymasz ponownie wywołania getItem, jeśli adapter ma już wewnętrzne odwołanie do niego i nie powinieneś tego zmieniać. Zamiast tego możesz uzyskać fragment, którego jest używany, patrząc na tę inną metodę instantiateItem (), która zostanie wywołana dla każdego z twoich fragmentów.
Mam nadzieję, że to pomoże każdemu.
źródło
Ponieważ FragmentManager zajmie się przywróceniem twoich fragmentów, gdy tylko zostanie wywołana metoda onResume (), mam wywołanie fragmentu do działania i dodanie go do listy. W moim przypadku przechowuję to wszystko w mojej implementacji PagerAdapter. Każdy fragment zna swoją pozycję, ponieważ jest dodawany do argumentów fragmentu podczas tworzenia. Teraz, ilekroć muszę manipulować fragmentem w określonym indeksie, wszystko, co muszę zrobić, to użyć listy z mojego adaptera.
Poniżej znajduje się przykład adaptera dla niestandardowego ViewPagera, który będzie powiększał fragment, gdy przejdzie do fokusu, i skaluje go w dół, gdy zniknie. Oprócz klas Adapter i Fragment mam tutaj wszystko, czego potrzebujesz, aby aktywność nadrzędna mogła odwoływać się do zmiennej adaptera i gotowe.
Adapter
Fragment
źródło
Moje rozwiązanie: ustawiam prawie każdy widok jako
static
. Teraz moja aplikacja działa idealnie. Możliwość wywoływania metod statycznych z dowolnego miejsca może nie jest dobrym stylem, ale po co bawić się kodem, który nie działa? Przeczytałem wiele pytań i ich odpowiedzi tutaj na SO i żadne rozwiązanie nie przyniosło sukcesu (dla mnie).Wiem, że może wyciekać pamięć i stertę odpadów, a mój kod nie będzie pasował do innych projektów, ale nie boję się tego - testowałem aplikację na różnych urządzeniach i warunkach, żadnych problemów, z Androidem Platforma wydaje się być w stanie sobie z tym poradzić. Interfejs użytkownika jest odświeżany co sekundę, a nawet na urządzeniu S2 ICS (4.0.3) aplikacja jest w stanie obsłużyć tysiące znaczników geograficznych.
źródło
Napotkałem ten sam problem, ale mój ViewPager znajdował się wewnątrz TopFragment, który utworzył i ustawił adapter przy użyciu
setAdapter(new FragmentPagerAdapter(getChildFragmentManager()))
.Naprawiłem ten problem, zastępując
onAttachFragment(Fragment childFragment)
w TopFragment w następujący sposób:Jak już wiadomo (patrz odpowiedzi powyżej), kiedy childFragmentManager odtwarza się, tworzy również fragmenty, które znajdowały się wewnątrz viewPager.
Ważną częścią jest to, że po tym wywołuje onAttachFragment i mamy teraz odniesienie do nowo utworzonego fragmentu!
Mam nadzieję, że pomoże to każdemu, kto zdobędzie ten stary Q, tak jak ja :)
źródło
Rozwiązałem problem zapisując fragmenty w SparceArray:
źródło
Tak, żebyś wiedział...
Dodając do litanii nieszczęść z tymi klasami, istnieje dość interesujący błąd, o którym warto się podzielić.
Używam ViewPagera do poruszania się po drzewie elementów (wybierz element, a widok pagera animuje przewijanie w prawo i pojawia się następna gałąź, przejdź wstecz, a ViewPager przewija się w przeciwnym kierunku, aby powrócić do poprzedniego węzła) .
Problem pojawia się, gdy wypycham i wyrywam fragmenty z końca FragmentStatePagerAdapter. Jest wystarczająco sprytny, aby zauważyć, że przedmioty się zmieniają, i wystarczająco inteligentny, aby utworzyć i zastąpić fragment, gdy przedmiot się zmieni. Ale niewystarczająco inteligentny, aby odrzucić stan fragmentu, ani wystarczająco inteligentny, aby przyciąć wewnętrznie zapisane stany fragmentów, gdy zmienia się rozmiar adaptera. Więc kiedy zdejmiesz element i wepchniesz nowy na koniec, fragment nowego elementu otrzyma zapisany stan fragmentu starego elementu, co spowodowało całkowite spustoszenie w moim kodzie. Moje fragmenty zawierają dane, których pobranie z Internetu może wymagać dużo pracy, więc nie zapisywanie stanu tak naprawdę nie wchodziło w grę.
Nie mam prostego rozwiązania. Użyłem czegoś takiego:
Niedoskonałe rozwiązanie, ponieważ nowa instancja fragmentu nadal otrzymuje pakiet saveState, ale przynajmniej nie zawiera przestarzałych danych.
źródło
Ponieważ ludzie nie czytają komentarzy, oto odpowiedź, która w większości powiela to, co tutaj napisałem :
główną przyczyną problemu jest fakt, że system Android nie wywołuje w
getItem
celu uzyskania fragmentów, które są faktycznie wyświetlane, aleinstantiateItem
. Ta metoda najpierw próbuje wyszukać i ponownie użyć wystąpienia fragmentu dla danej karty wFragmentManager
. Dopiero gdy to wyszukiwanie się nie powiedzie (co dzieje się tylko przy pierwszymFragmentManager
utworzeniu), wówczasgetItem
jest wywoływana. Z oczywistych powodów nie należy odtwarzać fragmentów (które mogą być ciężkie), na przykład za każdym razem, gdy użytkownik obraca swoje urządzenie.Aby rozwiązać ten problem, zamiast tworzyć fragmenty
Fragment.instantiate
w swoim działaniu, powinieneś to zrobić w miejscu, w którym fragmenty są naprawdę tworzone przy użyciu odpowiednich konstruktorów.pagerAdapter.instantiateItem
a wszystkie te wywołania powinny być otoczonestartUpdate/finishUpdate
wywołaniami metod, które odpowiednio rozpoczynają / zatwierdzają transakcję fragmentu.getItem
źródło