ViewPager i fragmenty - jaki jest właściwy sposób na zapisanie stanu fragmentu?
Fragmenty wydają się bardzo dobre do dzielenia logiki interfejsu użytkownika na niektóre moduły. Ale wraz z
ViewPagerjego cykl życia jest dla mnie nadal niejasny. Zatem myśli guru są niezbędne!
Edytować
>
Zobacz głupie rozwiązanie poniżej ;-)
Skala
>
Główne działanie zawiera
ViewPagerz fragmentami. Te fragmenty mogą implementować nieco inną logikę dla innych (nadpisanych) akcji, więc dane fragmentu są wypełniane przez interfejs wywołania zwrotnego wewnątrz akcji. I wszystko działa dobrze przy pierwszym uruchomieniu, ale! ....
Problem
>
Kiedy akcja jest odtwarzana (na przykład, gdy zmienia się orientacja), to samo dzieje się z fragmentami
ViewPager. Kod (znajdziesz poniżej) mówi, że za każdym razem, gdy tworzone jest działanie, próbuję utworzyć nowy adapter
ViewPagerFragment, taki sam jak Fragments (może to jest problem), ale FragmentManager już gdzieś przechowuje te wszystkie fragmenty (gdzie?) i uruchamia dla nich silnik odtwarzania. Tak więc silnik odtwarzania wywołuje "stare" fragmenty onAttach, onCreateView, itp. Używając mojego interfejsu wywołania zwrotnego, wywołaj inicjalizację danych za pomocą zaimplementowanej metody akcji. Ale ta metoda wskazuje na nowo utworzony fragment, który jest tworzony za pomocą metody onCreate działania.
Pytanie
>
Może używam złych szablonów, ale nawet książka o Androidzie 3 Pro niewiele o tym mówi. W związku z tym ,
Zapraszamy
, daj mi jedno lub dwa i pokaż mi, jak to zrobić dobrze. Wielkie dzięki!
Kod
>Podstawowa działalność
public class DashboardActivity extends BasePagerActivity implements OnMessageListActionListener {private MessagesFragment mMessagesFragment;@OverridePomocnik BasePagerActivity aka
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);
}
public class BasePagerActivity extends FragmentActivity {BasePagerAdapter mPagerAdapter;Adapter
ViewPager mPager;
}
public class BasePagerAdapter extends FragmentPagerAdapter implements TitleProvider {private Map<String, Fragment> mScreens;public BasePagerAdapter(Map<String, Fragment> screenMap, FragmentManager fm) { super(fm);Fragment
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
}}
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... */
}
Decyzja
>
Głupim rozwiązaniem jest przechowywanie fragmentów w onSaveInstanceState (aktywność hosta) za pomocą putFragment i umieszczanie ich wewnątrz onCreate przez getFragment. Ale nadal mam dziwne przeczucie, że to nie powinno działać w ten sposób ... Zobacz kod poniżej:
@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();
...
}
Nie znaleziono powiązanych wyników
Zaproszony:
Aby odpowiedzieć na pytania, Zaloguj się lub Zarejestruj się
10 odpowiedzi
Anonimowy użytkownik
Potwierdzenie od:
Nawet jeśli nie używaliśmy , nie warto za każdym razem tworzyć nowego fragmentu w . Jak już zauważyłeś, po dodaniu fragmentu do FragmentManager zostanie on dla Ciebie odtworzony po obróceniu i nie ma potrzeby dodawania go ponownie. Jest to częsta przyczyna błędów podczas pracy z fragmentami.
Typowe podejście do pracy z fragmentami to:
Korzystając z , przenosimy kontrolę nad fragmentami na adapter i nie musimy wykonywać powyższych kroków. Domyślnie wstępnie ładuje tylko jeden fragment z przodu iz tyłu bieżącej pozycji (chociaż nie niszczy ich, chyba że użyjesz ). Jest to kontrolowane
ViewPager.setOffscreenPageLimit(int)
http://developer.android.com/r ... 29... Z tego powodu bezpośrednie wywołanie metod na fragmentach poza adapterem nie jest gwarantowane, ponieważ mogą one nawet nie istnieć.
Krótko mówiąc, twoja decyzja o użyciu , aby móc później pobrać link, nie jest tak szalona i nie różni się zbytnio od zwykłego sposobu używania fragmentów (powyżej). W przeciwnym razie trudno jest uzyskać link, ponieważ fragment jest dodawany przez adapter, a nie przez Ciebie osobiście. Po prostu upewnij się, że jest wystarczająco wysoki, aby załadować żądane fragmenty w dowolnym momencie, ponieważ polegasz na jego obecności. Pomija to możliwości leniwego ładowania ViewPagera, ale wygląda na to, że właśnie tego chcesz dla swojej aplikacji.
Innym podejściem jest przesłonięcie i zachowanie odniesienia do fragmentu zwróconego z supercall przed zwróceniem go (ma logikę do znalezienia fragmentu, jeśli jest już obecny).
Aby uzyskać pełniejszy obraz, przyjrzyj się niektórym źródłom.
FragmentPagerAdapter
http://grepcode.com/file/repos ... v%3Df
(krótki i
ViewPager
http://grepcode.com/file/repos ... v%3Df
(długo).
Anonimowy użytkownik
Potwierdzenie od:
świetna odpowiedź
https://stackoverflow.com/a/9646622/708906
i wzmianka o zastępowaniu w celu zachowania linków do wygenerowanych , aby można było nad nimi pracować później. Powinno to również działać z ; Szczegółowe informacje można znaleźć w uwagach.
Oto prosty przykład, jak uzyskać link zwracany przez , który jest niezależny od wewnętrznego zestawu w . Kluczem jest zastąpienie
http://grepcode.com/file/repos ... %2383
i zapisz tam linki,
i
nie w .
lub
jeśli wolisz pracować z zamiast ze zmiennymi składowymi klasy/odwołaniami do , możesz również pobrać ustawione przez w ten sam sposób:
UWAGA: nie dotyczy to , ponieważ nie ustawia podczas tworzenia swoich .
Zauważ, że ta metoda nie polega na naśladowaniu wewnętrznego taguustawionego przez , ale zamiast tego używa odpowiednich interfejsów API do ich pobierania. Dzięki temu, nawet jeśli zmieni się w przyszłych wersjach , nadal będziesz bezpieczny.
Nie zapomnij
że w zależności od projektu , nad którymi próbujesz pracować, mogą istnieć lub nie istnieć, więc powinieneś wziąć to pod uwagę, ustawiając przed użyciem linków.
Co więcej, jeśli zamiast
Ponieważ pracujesz z , nie chcesz przechowywać twardych linków do swoich , ponieważ możesz ich mieć dużo i twarde linki niepotrzebnie będą przechowywać je w pamięci . Zamiast tego zachowaj odwołania w zmiennych zamiast domyślnych. Podobne do tego:
Anonimowy użytkownik
Potwierdzenie od:
Jak widać z
kod źródłowy FragmentPagerAdapter
http://grepcode.com/file/repos ... 28int,int%29, fragmenty sterowane przez są przechowywane w pod tagiem wygenerowanym przez:
to , to Twoja instancja . to pozycja fragmentu. Dlatego możesz przechowywać identyfikator obiektu w : [/code]
Jeśli chcesz utworzyć link do tego fragmentu, możesz pobrać if z , na przykład:
Anonimowy użytkownik
Potwierdzenie od:
Mój przypadek
- Tworzę/dodaję strony dynamicznie i przenoszę je do ViewPager, ale po obróceniu (onConfigurationChange) otrzymuję nową stronę, ponieważ oczywiście OnCreate jest wywoływany ponownie. Ale chcę zachować łącze 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 jakoś przechowywanie referencji w tablicy, która zostanie przywrócona po przestawieniu/skonfigurowaniu.
Objazd
koncepcja klucza ścieżki polegała na tym, że akcja (która renderuje fragmenty) zarządza również tablicą odwołań do istniejących fragmentów, ponieważ ta akcja może używać pakietów w onSaveInstanceState
Dlatego w ramach tego działania deklaruję prywatnemu członkowi śledzenie otwartych stron
Jest to aktualizowane 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łównym działaniu, więc potrzebowałem członków i metod w moim FragmentPagerAdapter, aby to działało, więc wewnątrz
identyczna konstrukcja (jak pokazano powyżej w MainActivity)
i ta synchronizacja (jak opisano powyżej w onSaveInstanceState) jest obsługiwana właśnie przez te metody
Wreszcie w klasie fragmentów
były dwie zmiany, aby to zadziałało, po pierwsze
a następnie dodaj to do onCreate, aby fragmenty nie zostały zniszczone
Nadal jestem w trakcie owijania głowy wokół fragmentów i cyklu życia Androida, więc zastrzeżenie jest takie, że w tej metodzie mogą występować nadmiarowości/nieefektywności. Ale to działa dla mnie i mam nadzieję, że może być pomocne dla innych osób z podobnymi przypadkami.
Anonimowy użytkownik
Potwierdzenie od:
Nie możesz ich usunąć w , w przeciwnym razie otrzymasz ten wyjątek:
Can not perform this action after Oto kod w adapterze strony:
Zapisuję bieżącą stronę i przywracam ją w po utworzeniu fragmentów.
Anonimowy użytkownik
Potwierdzenie od:
Przykładowy kod do korzystania z można znaleźć
tutaj
http://developer.android.com/t ... mples
Prawdą jest, że zarządzanie fragmentami w pagerze widoków między instancjami działań jest nieco skomplikowane, ponieważ we frameworku dba o zapisanie stanu i przywrócenie wszystkich aktywnych fragmentów utworzonych przez pager. Wszystko to naprawdę oznacza, że po zainicjowaniu adapter musi upewnić się, że łączy się ponownie z odzyskanymi fragmentami. Możesz spojrzeć na kod lub , aby zobaczyć, jak to się robi.
Anonimowy użytkownik
Potwierdzenie od:
Upewnij się, że wywołałeś PRZED wywołaniem Wywołując ... ViewPager
natychmiast
spojrzy na jego adapter i spróbuje zdobyć jego fragmenty. Może się to zdarzyć, zanim ViewPager będzie miał szansę 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).
Anonimowy użytkownik
Potwierdzenie od:
To jest kod adaptera (nic dziwnego, poza tym, że to lista fragmentów obsługiwanych przez działanie)
Cały problem z tym wątkiem polega na uzyskaniu odniesienia do „starych” fragmentów, więc używam tego kodu w działaniu onCreate.
Oczywiście w razie potrzeby możesz dodatkowo dostosować ten kod, na przykład upewniając się, że fragmenty są instancjami określonej klasy.
Anonimowy użytkownik
Potwierdzenie od:
Aby uzyskać trochę więcej przetwarzania, napisałem własną ArrayList dla mojego PageAdapter, aby uzyskać fragment przez viewPagerId i FragmentClass w dowolnej pozycji:
Po prostu utwórz MyPageArrayList z fragmentami:
i dodaj je do viewPager:
po tym można uzyskać po orientacji zmienić właściwy fragment używając jego klasy:
Anonimowy użytkownik
Potwierdzenie od:
przed twoimi zajęciami.
nie działa coś takiego: