Korzystam z ViewPager z biblioteki kompatybilności. Udało mi się wyświetlić kilka widoków, które mogę przewijać.
Trudno mi jednak wymyślić, jak zaktualizować ViewPager za pomocą nowego zestawu widoków.
Próbowałem różnych rzeczy, takich jak dzwonienie mAdapter.notifyDataSetChanged()
, mViewPager.invalidate()
nawet tworząc zupełnie nowy adapter za każdym razem, gdy chcę użyć nowej listy danych.
Nic nie pomogło, widoki tekstowe pozostają niezmienione od pierwotnych danych.
Aktualizacja: Zrobiłem mały projekt testowy i prawie byłem w stanie zaktualizować widoki. Wkleję klasę poniżej.
To, co nie wydaje się aktualizować, to drugi widok, pozostaje „B”, powinien on wyświetlać „Y” po naciśnięciu przycisku aktualizacji.
public class ViewPagerBugActivity extends Activity {
private ViewPager myViewPager;
private List<String> data;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
data = new ArrayList<String>();
data.add("A");
data.add("B");
data.add("C");
myViewPager = (ViewPager) findViewById(R.id.my_view_pager);
myViewPager.setAdapter(new MyViewPagerAdapter(this, data));
Button updateButton = (Button) findViewById(R.id.update_button);
updateButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
updateViewPager();
}
});
}
private void updateViewPager() {
data.clear();
data.add("X");
data.add("Y");
data.add("Z");
myViewPager.getAdapter().notifyDataSetChanged();
}
private class MyViewPagerAdapter extends PagerAdapter {
private List<String> data;
private Context ctx;
public MyViewPagerAdapter(Context ctx, List<String> data) {
this.ctx = ctx;
this.data = data;
}
@Override
public int getCount() {
return data.size();
}
@Override
public Object instantiateItem(View collection, int position) {
TextView view = new TextView(ctx);
view.setText(data.get(position));
((ViewPager)collection).addView(view);
return view;
}
@Override
public void destroyItem(View collection, int position, Object view) {
((ViewPager) collection).removeView((View) view);
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public Parcelable saveState() {
return null;
}
@Override
public void restoreState(Parcelable arg0, ClassLoader arg1) {
}
@Override
public void startUpdate(View arg0) {
}
@Override
public void finishUpdate(View arg0) {
}
}
}
android
android-viewpager
C0deAttack
źródło
źródło
Odpowiedzi:
Można to osiągnąć na kilka sposobów.
Pierwsza opcja jest łatwiejsza, ale nieco bardziej nieefektywna.
Zastąp
getItemPosition
wPagerAdapter
ten sposób:W ten sposób, kiedy zadzwonisz
notifyDataSetChanged()
, pager widoku usunie wszystkie widoki i załaduje je ponownie. W ten sposób uzyskuje się efekt przeładowania.Druga opcja sugerowane przez Alvaro Luis Bustamante (poprzednio alvarolb) , jest
setTag()
sposobem, winstantiateItem()
przypadku instancji nowego widoku. Następnie zamiast używaćnotifyDataSetChanged()
możeszfindViewWithTag()
znaleźć widok, który chcesz zaktualizować.Drugie podejście jest bardzo elastyczne i wydajne. Uznanie dla alvarolba za oryginalne badania.
źródło
Nie wydaje mi się, aby w pliku był jakikolwiek błąd
PagerAdapter
. Problem polega na tym, że zrozumienie, jak to działa, jest trochę skomplikowane. Patrząc na wyjaśnione tutaj rozwiązania, istnieje nieporozumienie, a zatem z mojego punktu widzenia słabe wykorzystanie utworzonych widoków.W ciągu ostatnich kilku dni pracowałem z
PagerAdapter
iViewPager
znalazłem następujące:Ta
notifyDataSetChanged()
metodaPagerAdapter
powiadomi tylko,ViewPager
że strony znajdujące się poniżej uległy zmianie. Na przykład, jeśli utworzyłeś / usunąłeś strony dynamicznie (dodając lub usuwając elementy z listy),ViewPager
powinieneś się tym zająć. W tym przypadku myślę, żeViewPager
określa, czy nowy widok powinien zostać usunięty lub utworzony za pomocą metodgetItemPosition()
igetCount()
.Myślę, że
ViewPager
po odebraniunotifyDataSetChanged()
połączenia jego widok dziecka jest sprawdzany za pomocągetItemPosition()
. Jeśli dla widoku podrzędnego ta metoda zwracaPOSITION_NONE
, oznacza,ViewPager
że widok został usunięty, wywołującdestroyItem()
i usuwając ten widok.W ten sposób zastąpienie opcji
getItemPosition()
powrotu zawszePOSITION_NONE
jest całkowicie niepoprawne, jeśli chcesz tylko zaktualizować zawartość stron, ponieważ wcześniej utworzone widoki zostaną zniszczone, a nowe będą tworzone za każdym razem, gdy zadzwonisznotifyDatasetChanged()
. Może się wydawać, że nie jest tak źle tylko przez kilkaTextView
sekund, ale jeśli masz złożone widoki, takie jak ListViews wypełnione z bazy danych, może to być prawdziwy problem i marnowanie zasobów.Istnieje więc kilka podejść do skutecznej zmiany zawartości widoku bez konieczności jego usuwania i tworzenia ponownie. To zależy od problemu, który chcesz rozwiązać. Moje podejście polega na użyciu tej
setTag()
metody w dowolnym utworzonym widoku tejinstantiateItem()
metody. Więc jeśli chcesz zmienić dane lub unieważnić potrzebny widok, możesz wywołaćfindViewWithTag()
metodę wViewPager
celu pobrania wcześniej utworzonego widoku i zmodyfikować / użyć go, jak chcesz, bez konieczności usuwania / tworzenia nowego widoku za każdym razem, gdy chcesz zaktualizować jakąś wartość.Wyobraź sobie na przykład, że masz 100 stron po 100
TextView
s i chcesz okresowo aktualizować tylko jedną wartość. Dzięki omówionym wcześniej metodom oznacza to, że usuwasz i tworzysz 100 sekundTextView
dla każdej aktualizacji. To nie ma sensu...źródło
setTag
w metodzie.onInstantiateItem
Czy chcesz zaktualizować tę odpowiedź za pomocą kodowania? Dzięki.Zmień
FragmentPagerAdapter
TOFragmentStatePagerAdapter
.Przesłoń
getItemPosition()
metodę i zwróćPOSITION_NONE
.W końcu będzie nasłuchiwać
notifyDataSetChanged()
pager na widoku.źródło
Odpowiedź udzielona przez alvarolb jest zdecydowanie najlepszym sposobem na to. Opierając się na jego odpowiedzi, łatwym sposobem na wdrożenie tego jest po prostu przechowywanie aktywnych widoków według pozycji:
Następnie raz, zastępując
notifyDataSetChanged
metodę, możesz odświeżyć widoki ...Możesz użyć podobnego kodu
instantiateItem
inotifyDataSetChanged
odświeżyć widok. W moim kodzie używam dokładnie tej samej metody.źródło
Miałem ten sam problem. Dla mnie zadziałało rozszerzenie FragmentStatePagerAdapter i zastąpienie poniższych metod:
źródło
Po godzinach frustracji podczas próby wszystkich powyższych rozwiązań w celu przezwyciężenia tego problemu, a także próbują wiele rozwiązań na innych podobnych pytań, takich jak ten , ten i ten , który wszystko FAILED ze mną, aby rozwiązać ten problem i aby
ViewPager
zniszczyć staryFragment
i wypełnićpager
z nowyFragment
s. Rozwiązałem problem w następujący sposób:1) Ustaw
ViewPager
klasę na rozszerzeniaFragmentPagerAdapter
w następujący sposób:2) Utwórz element dla
ViewPager
tego skleputitle
ifragment
następujące elementy:3) Ustaw konstruktor
ViewPager
weź mojąFragmentManager
instancję, aby przechował ją wclass
następujący sposób:4) Tworzenie metodę do ponownego ustawiania
adapter
danych z nowych danych, usuwając wszystkie poprzedniefragment
zefragmentManager
sobą bezpośrednio, abyadapter
ustawić nowyfragment
z nową listą ponownie w następujący sposób:5) Z kontenera
Activity
lubFragment
nie inicjuj ponownie adaptera nowymi danymi. Ustaw nowe dane za pomocą metodysetPagerItems
z nowymi danymi w następujący sposób:Mam nadzieję, że to pomoże.
źródło
Miałem ten sam problem i moje rozwiązanie korzysta
FragmentPagerAdapter
z zastępowaniaFragmentPagerAdapter#getItemId(int position)
:Domyślnie ta metoda zwraca pozycję elementu. Przypuszczam, że
ViewPager
sprawdza, czyitemId
został zmieniony i odtwarza stronę tylko wtedy, gdy tak było. Ale wersja bez przesłonięcia zwraca tę samą pozycję,itemId
nawet jeśli strona jest faktycznie inna, a ViewPager nie definiuje, że strona jest zastępowana i należy ją odtworzyć.Aby tego użyć,
long id
jest potrzebny na każdej stronie. Zwykle oczekuje się, że będzie unikalny, ale w tym przypadku sugeruję, że powinien on różnić się od poprzedniej wartości dla tej samej strony. Możliwe jest więc użycie licznika ciągłego w adapterze lub liczbach całkowitych losowych (o szerokim rozkładzie).Myślę, że jest to bardziej spójny sposób, a raczej użycie tagów widoku wspomnianych jako rozwiązanie w tym temacie. Ale prawdopodobnie nie we wszystkich przypadkach.
źródło
PagerAdapter
. Co to za klasa?FragmentPagerAdapter
Znalazłem bardzo interesującą decyzję dotyczącą tego problemu. Zamiast używać FragmentPagerAdapter , który przechowuje w pamięci wszystkie fragmenty, możemy użyć FragmentStatePagerAdapter ( android.support.v4.app.FragmentStatePagerAdapter ), który przeładowuje fragment za każdym razem, gdy go wybieramy.
Realizacje obu adapterów są identyczne. Musimy więc po prostu zmienić „ przedłużyć FragmentPagerAdapter ” na „ przedłużyć FragmentStatePagerAdapter ”
źródło
Po wielu poszukiwaniach tego problemu znalazłem naprawdę dobre rozwiązanie, które moim zdaniem jest właściwą drogą do rozwiązania tego problemu. Zasadniczo instantiateItem jest wywoływany tylko wtedy, gdy widok jest tworzony i nigdy więcej, chyba że widok zostanie zniszczony (dzieje się tak, gdy przesłonisz funkcję getItemPosition, aby zwrócić POSITION_NONE). Zamiast tego zapisz utworzone widoki i zaktualizuj je w adapterze, wygeneruj funkcję get, aby ktoś inny mógł ją zaktualizować, lub ustaw funkcję, która aktualizuje adapter (moje ulubione).
Tak więc w MyViewPagerAdapter dodaj zmienną, taką jak:
w twoim instantiateItem:
w ten sposób możesz stworzyć funkcję, która zaktualizuje twój widok:
Mam nadzieję że to pomoże!
źródło
instantiateItem
, dlatego moje widoki nie były aktualizowane. Z twojej odpowiedzi zrozumiałem. +1Dwa i pół roku po tym, jak OP zadał swoje pytanie, ta kwestia wciąż jest, no cóż, nadal problemem. To oczywiste, że priorytet Google w tym zakresie nie jest szczególnie wysoki, więc zamiast znaleźć rozwiązanie, znalazłem obejście. Ogromnym przełomem było dla mnie odkrycie, jaka była prawdziwa przyczyna problemu (patrz zaakceptowana odpowiedź w tym poście ). Gdy stało się jasne, że problem polega na tym, że wszystkie aktywne strony nie są odpowiednio odświeżane, moje obejście było oczywiste:
W moim fragmencie (na stronach):
W mojej działalności, gdzie ładuję strony:
Po tym, gdy przeładujesz drugi zestaw stron, błąd nadal spowoduje, że niektóre wyświetlą stare dane. Zostaną one jednak odświeżone i zobaczysz nowe dane - użytkownicy nie będą wiedzieli, że strona była zawsze niepoprawna, ponieważ odświeżenie nastąpi zanim zobaczą stronę.
Mam nadzieję, że to komuś pomoże!
źródło
Wszystkie te rozwiązania mi nie pomogły. dlatego znalazłem działające rozwiązanie: możesz za
setAdapter
każdym razem, ale to nie wystarczy. powinieneś to zrobić przed zmianą adaptera:a potem:
źródło
ViewPager
fragmentu wewnętrznego, więc zastąpiłemslideShowPagerAdapter.getFragmentManager()
gogetChildFragmentManager()
. MożegetFragmentManager()
pomoże w twoim przypadku. UżyłemFragmentPagerAdapter
nieFragmentStatePagerAdapter
. Zobacz także stackoverflow.com/a/25994654/2914140 na hacky sposób.Znacznie łatwiejszy sposób: użyj
FragmentPagerAdapter
i zawiń stronicowane widoki na fragmenty. Są aktualizowaneźródło
Dziękujemy rui.araujo i Alvaro Luis Bustamante. Na początku staram się używać sposobu rui.araujo, ponieważ jest to łatwe. Działa, ale gdy dane się zmienią, strona przerysuje oczywiście. Jest źle, więc staram się używać sposobu Alvaro Luisa Bustamante. Jest idealny. Oto kod:
A kiedy zmieniają się dane:
źródło
Na wypadek, gdyby ktoś używał adaptera opartego na FragmentStatePagerAdapter (który pozwoli ViewPagerowi stworzyć minimum stron potrzebnych do wyświetlania, maksymalnie 2 w moim przypadku), odpowiedź @ rui.araujo na nadpisanie getItemPosition w twoim adapterze nie spowoduje znacznych strat, ale nadal można ulepszyć.
W pseudokodzie:
źródło
getItemPosition()
przypadku zmiany zestawu danych, a ViewPager będzie wiedział, że zostały zmienione, czy nie. niestety ViewPager tego nie zrobił.Miałem podobny problem, w którym miałem cztery strony, a jedna ze stron zaktualizowała widoki w pozostałych trzech. Udało mi się zaktualizować widżety (SeekBars, TextViews itp.) Na stronie sąsiadującej z bieżącą stroną. Ostatnie dwie strony miałyby niezainicjowane widżety podczas połączenia
mTabsAdapter.getItem(position)
.Aby rozwiązać problem, skorzystałem z niego
setSelectedPage(index)
przed zadzwonieniemgetItem(position)
. Spowoduje to utworzenie instancji strony, co pozwoli mi zmieniać wartości i widżety na każdej stronie.Po wszystkich aktualizacjach użyłbym,
setSelectedPage(position)
a następnienotifyDataSetChanged()
.Możesz zobaczyć lekkie migotanie w ListView na głównej stronie aktualizacji, ale nic zauważalnego. Nie przetestowałem tego gruntownie, ale to rozwiązuje mój bezpośredni problem.
źródło
Właśnie zamieszczam tę odpowiedź na wypadek, gdyby ktokolwiek uznał ją za przydatną. Aby zrobić dokładnie to samo, po prostu wziąłem kod źródłowy ViewPager i PagerAdapter z biblioteki kompatybilności i skompilowałem go w moim kodzie (musisz samodzielnie rozwiązać wszystkie błędy i zaimportować, ale na pewno da się to zrobić).
Następnie w CustomViewPager utwórz metodę o nazwie updateViewAt (pozycja int). Sam widok można uzyskać z ArrayList mItems zdefiniowanych w klasie ViewPager (musisz ustawić identyfikator dla widoków w natychmiastowym elemencie i porównać ten identyfikator z pozycją w metodzie updateViewAt ()). Następnie możesz zaktualizować widok w razie potrzeby.
źródło
Chyba mam logikę ViewPager.
Jeśli muszę odświeżyć zestaw stron i wyświetlić je w oparciu o nowy zestaw danych, wywołuję zawiadomienieDataSetChanged () . Następnie ViewPager wykonuje szereg wywołań funkcji getItemPosition () , przekazując tam Fragment jako Obiekt. Ten fragment może pochodzić ze starego zestawu danych (który chcę odrzucić) lub z nowego (który chcę wyświetlić). Zastępuję więc getItemPosition () i tam muszę jakoś ustalić, czy mój Fragment pochodzi ze starego zestawu danych, czy z nowego.
W moim przypadku mam układ 2-panelowy z listą górnych elementów w lewym panelu i widokiem przeciągnięcia (ViewPager) po prawej. Tak więc przechowuję link do mojego bieżącego najwyższego elementu w moim PagerAdapter, a także wewnątrz każdego utworzonego fragmentu strony. Gdy wybrany najwyższy element na liście zmienia się, zapisuję nowy najwyższy element w PagerAdapter i wywołuję powiadomienieDataSetChanged () . A w przesłoniętej funkcji getItemPosition () porównuję najwyższy element z mojego adaptera z najwyższym elementem z mojego fragmentu. I tylko jeśli nie są sobie równe, zwracam POSITION_NONE. Następnie PagerAdapter przywraca wszystkie fragmenty, które zwróciły POSITION_NONE.
UWAGA. Lepszym pomysłem może być przechowywanie identyfikatora górnego elementu zamiast odwołania.
Poniższy fragment kodu jest nieco schematyczny, ale dostosowałem go z faktycznie działającego kodu.
Dzięki za wszystkich poprzednich badaczy!
źródło
Poniższy kod działał dla mnie.
Utwórz klasę, która rozszerza klasę FragmentPagerAdapter, jak poniżej.
Następnie w każdym utworzonym fragmencie utwórz metodę updateFragment. W tej metodzie zmieniasz rzeczy, które musisz zmienić we fragmencie. Na przykład w moim przypadku Fragment0 zawierał GLSurfaceView, który wyświetla obiekt 3d oparty na ścieżce do pliku .ply, więc w mojej metodzie updateFragment zmieniam ścieżkę do tego pliku warstwy.
następnie utwórz instancję ViewPager,
i instancja Adpatera,
to zrób to,
Następnie wewnątrz klasy, jeśli zainicjalizowałeś klasę Adapter powyżej i utworzyłeś viewPager, za każdym razem, gdy chcesz zaktualizować jeden ze swoich fragmentów (w naszym przypadku Fragment0), użyj następujących poleceń:
To rozwiązanie zostało oparte na technice zaproponowanej przez Alvaro Luisa Bustamante.
źródło
1. Najpierw musisz ustawić metodę getItemposition w swojej klasie Pageradapter 2. Musisz odczytać dokładną pozycję swojego View Pager 3. następnie wyślij tę pozycję jako lokalizację danych nowego 4. Zapisz przycisk aktualizacji po kliknięciu słuchacza wewnątrz setonPageChange słuchacz
ten kod programu jest nieco zmodyfikowany, aby ustawić tylko określony element pozycji
źródło
szło mi to, co zadziałało
viewPager.getAdapter().notifyDataSetChanged();
oraz w adapterze umieszczając kod do aktualizacji widoku w ten
getItemPosition
sposóbmoże nie jest to najodpowiedniejszy sposób, ale zadziałało (
return POSITION_NONE
sztuczka spowodowała u mnie awarię, więc nie było opcji)źródło
Możesz dynamicznie aktualizować wszystkie fragmenty, które możesz zobaczyć w trzech krokach.
W twoim adapterze:
Teraz w twojej aktywności:
Wreszcie w twoim fragmencie coś takiego:
Pełny kod możesz zobaczyć tutaj .
Dzięki Alvaro Luis Bustamante.
źródło
Zawsze zwracanie
POSITION_NONE
jest proste, ale mało wydajne, ponieważ wywołuje tworzenie instancji wszystkich stron, które już zostały utworzone.Utworzyłem bibliotekę ArrayPagerAdapter, aby dynamicznie zmieniać elementy w PagerAdapters.
Wewnętrznie, adaptery Ta biblioteka jest powrót
POSITION_NONE
nagetItemPosiition()
tylko wtedy, gdy jest to konieczne.Korzystając z tej biblioteki, możesz dynamicznie zmieniać elementy, takie jak śledzenie.
Biblioteka Thils obsługuje również strony utworzone przez Fragmenty.
źródło
To jest dla wszystkich takich jak ja, którzy muszą zaktualizować Viewpager z usługi (lub innego wątku w tle) i żadna z propozycji nie zadziałała: po pewnym czasie logowania zauważyłem, że metoda powiadomienieDataSetChanged () nigdy nie zwraca. getItemPosition (obiekt Object) nazywa się tam wszystkie końce bez dalszego przetwarzania. Następnie znalazłem w dokumentach nadrzędnej klasy PagerAdapter (nie ma go w dokumentach podklas): „Zmiany zestawu danych muszą nastąpić w głównym wątku i muszą kończyć się wywołaniem powiadomieniaDataSetChanged ()”. Tak więc działającym rozwiązaniem w tym przypadku było (użycie FragmentStatePagerAdapter i getItemPosition (obiekt obiektu) ustawione na zwrócenie POSITION_NONE):
a następnie wywołanie powiadomieniaDataSetChanged ():
źródło
Możesz dodać transformację pagera do Viewpager w ten sposób
W poniższym kodzie zmieniłem kolor widoku w czasie wykonywania podczas przewijania pagera
źródło
Wiem, że jestem późno, ale to może komuś pomóc. Po prostu rozszerzam akcentującą odpowiedź i dodałem również komentarz do niej.
dobrze,
sama odpowiedź mówi, że jest nieefektywna
więc aby to zrobić tylko raz, gdy jest to wymagane, możesz to zrobić
źródło
ViewPager nie został zaprojektowany do obsługi dynamicznej zmiany widoku.
Miałem to potwierdzenie, szukając innego błędu związanego z tym https://issuetracker.google.com/issues/36956111, aw szczególności https://issuetracker.google.com/issues/36956111#comment56
To pytanie jest nieco stare, ale Google niedawno rozwiązało ten problem z ViewPager2 . Pozwoli to zastąpić ręcznie wykonane (nieobsługiwane i potencjalnie błędne) rozwiązania standardowymi. Zapobiega także niepotrzebnemu odtwarzaniu widoków, jak to robią niektóre odpowiedzi.
Przykłady ViewPager2 można sprawdzić https://github.com/googlesamples/android-viewpager2
Jeśli chcesz korzystać z ViewPager2, musisz dodać następującą zależność w pliku build.gradle:
Następnie możesz zastąpić ViewPager w pliku xml:
Następnie będziesz musiał zastąpić ViewPager przez ViewPager2 w swojej działalności
ViewPager2 potrzebuje RecyclerView.Adapter lub FragmentStateAdapter, w twoim przypadku może to być RecyclerView.Adapter
W przypadku korzystania z TabLayout, możesz użyć TabLayoutMediator:
Następnie będziesz mógł odświeżyć swoje widoki, modyfikując dane adaptera i wywołując metodę replaceDataSetChanged
źródło
Zamiast zwracać
POSITION_NONE
i ponownie tworzyć wszystkie fragmenty, możesz zrobić tak, jak zasugerowałem tutaj: dynamicznie aktualizować ViewPager?źródło
Myślę, że podjąłem prosty sposób powiadamiania o zmianach w zestawie danych:
Najpierw zmień nieco sposób działania funkcji instantiateItem:
w przypadku „updateView” wypełnij widok wszystkimi danymi, które chcesz wypełnić (setText, setBitmapImage, ...).
sprawdź, czy destroyView działa w następujący sposób:
Załóżmy teraz, że musisz zmienić dane, zrób to, a następnie wywołaj następną funkcję na PagerAdapter:
Na przykład, jeśli chcesz powiadomić wszystkie widoki wyświetlane przez viewPager, że coś się zmieniło, możesz wywołać:
Otóż to.
źródło
Jeśli chodzi o to, co jest warte, na KitKat + wydaje się, że
adapter.notifyDataSetChanged()
to wystarczy, aby wyświetlić nowe widoki, pod warunkiem, że maszsetOffscreenPageLimit
wystarczająco wysoki poziom. Jestem w stanie uzyskać pożądane zachowanieviewPager.setOffscreenPageLimit(2)
.źródło
I rzeczywiście wykorzystać
notifyDataSetChanged()
naViewPager
iCirclePageIndicator
, a potem zadzwoniędestroyDrawingCache()
naViewPager
i to działa .. Żaden z innymi rozwiązaniami pracował dla mnie.źródło