ViewPager nie przerysowuje treści, pozostaje / staje się pusty

86

Mamy tutaj bardzo dziwny problem z ViewPager. Osadzamy listy na każdej stronie ViewPager i wyzwalamy notifyDataSetChanged zarówno w adapterze list, jak i adapterze pagera widoku podczas aktualizowania danych listy.

Zauważyliśmy, że czasami strona nie aktualizuje swojego drzewa widoku, tzn. Pozostaje pusta, a czasami nawet znika po przejściu do niej. Podczas kilkukrotnego przewijania w tę iz powrotem zawartość nagle pojawi się ponownie. Wygląda na to, że Android nie ma tutaj aktualizacji widoku. Zauważyłem również, że podczas debugowania za pomocą przeglądarki hierarchii wybranie widoku zawsze spowoduje, że pojawi się on ponownie, najwyraźniej dlatego, że przeglądarka hierarchii zmusza wybrany widok do ponownego rysowania.

Nie mogłem jednak zrobić tego programowo; unieważnienie widoku listy lub nawet całego pagera widoku nie przyniosło żadnego efektu.

Dzieje się tak z biblioteką compliance-v4_r7. Próbowałem również użyć najnowszej wersji, ponieważ twierdzi, że naprawia wiele problemów związanych z wyświetlaniem pagera, ale to pogorszyło sprawę (na przykład gesty zostały zepsute, aby czasami nie pozwalały mi przeglądać wszystkich stron).

Czy ktoś inny też ma te problemy, czy masz pojęcie, co może je powodować?

Matthias
źródło

Odpowiedzi:

44

W końcu udało się znaleźć rozwiązanie. Najwyraźniej nasza implementacja miała dwa problemy:

  1. nasz adapter nie usunął widoku w formacie destroyItem().
  2. buforowaliśmy widoki, abyśmy musieli tylko raz nadmuchać nasz układ, a ponieważ nie usuwaliśmy widoku w programie destroyItem(), nie dodawaliśmy go, instantiateItem()tylko zwracaliśmy buforowany widok odpowiadający bieżącej pozycji.

Nie zaglądałem zbyt głęboko do kodu źródłowego ViewPager- i nie jest do końca jasne, że musisz to zrobić - ale dokumentacja mówi:

destruItem ()
Usuwa stronę dla danej pozycji. Adapter jest odpowiedzialny za usunięcie widoku z jego kontenera, chociaż musi zapewnić, że zostanie to zrobione tylko do czasu powrotu z finishUpdate (ViewGroup).

i:

Bardzo prosty PagerAdapter może zdecydować się na użycie samych widoków strony jako obiektów kluczowych, zwracając je z instancji instantiateItem (ViewGroup, int) po utworzeniu i dodaniu do nadrzędnej ViewGroup. Dopasowana implementacja destruktora (ViewGroup, int, Object) spowodowałaby usunięcie widoku z nadrzędnej grupy ViewGroup, a isViewFromObject (View, Object) można by zaimplementować jako zwracany widok == obiekt ;.

Więc mój wniosek jest taki, że ViewPageropiera się na swoim podstawowym adapterze, aby jawnie dodawać / usuwać swoje elementy podrzędne w instantiateItem()/ destroyItem(). Oznacza to, że jeśli adapter jest podklasą PagerAdapter, podklasa musi implementować tę logikę.

Uwaga dodatkowa: pamiętaj o tym, jeśli używasz list w środku ViewPager.

futtetennista
źródło
2
Mam ten sam problem, ale z FragmentPagerAdapter, który obsługuje dla mnie onDestroy. Żadne z innych rozwiązań tutaj również nie działa.
Greg Ennis,
14
Problemem może być również to, że ktoś używa getFragmentManger zamiast GetChildFragmentManager
Boy
@Boy co to jest GetChildFragmentManager?
pożar w dołku
1
@Boy Wooooow .... Wyrywałem sobie włosy przez całe dwa dni, próbując dowiedzieć się, dlaczego nie mogę nic zaktualizować. DZIĘKUJĘ CI! (naprawdę, naprawdę powinni umieścić w dokumentacji Google uwagę, aby używać childfragmentmanager, to nie oznacza, że ​​potrzebujesz innego menedżera).
user0721090601
@futtetennista Robię to samo… w obliczu tego problemu… czy możesz sprawdzić… stackoverflow.com/questions/61727835/…
AskQ
55

Jeśli ViewPagerjest ustawiona wewnątrz fragmentu z a FragmentPagerAdapter, użyj getChildFragmentManager()zamiast getSupportFragmentManager()jako parametru, aby zainicjować FragmentPagerAdapter.

mAdapter = new MyFragmentPagerAdapter(getChildFragmentManager());

Zamiast

mAdapter = new MyFragmentPagerAdapter(getSupportFragmentManager());
niewolnik
źródło
2
Dobry chwyt, wydaje się rozwiązać mój problem przy korzystaniu z FragmentPagerAdapter
Tobliug
1
Dzięki - to zadziałało. Starałem się wymusić getItem()na zasadzie FragmentPagerAdapter, która została zagnieżdżona w odtworzonym fragmentem.
kosiara - Bartosz Kosarzycki 22.04.2016
wow, to działa jak urok. Myślę, że problem jest spowodowany zawyżeniem podglądu we fragmencie
Thecarisma
Są takie chwile, jak ten. Żałuję, że nie mieliśmy klaskać jak Medium, więc mógłbym dać ci ponad 1000 klaśnięć za to.
Dobra
21

Miałem dokładnie ten sam problem, ale faktycznie zniszczyłem widok w destruItem (pomyślałem). Problemem jednak było to, że zniszczył go za pomocą viewPager.removeViewAt(index);instedviewPager.removeView((View) object);

Źle:

@Override
public void destroyItem(ViewGroup viewPager, int position, Object object) {
    viewPager.removeViewAt(position);
}

Dobrze:

@Override
public void destroyItem(ViewGroup viewPager, int position, Object object) {
    viewPager.removeView((View) object);
}
Ringen
źródło
Dzięki za podpowiedź. Zajmowałem się tym problemem od dłuższego czasu.
Moritz,
2
U mnie to zadziałało, ale czy wiesz, dlaczego ten pierwszy jest problemem?
HannahMitt
@HannahMitt removeViewAt usunie każdy widok znajdujący się w tej konkretnej pozycji w ramach bieżącej listy dzieci ViewPager, a strony mogą być dodawane do ViewPager w dowolnej kolejności (więc strona 0 może faktycznie znajdować się w ViewPager pod indeksem 1 lub gdziekolwiek). removeView przejrzy listę elementów podrzędnych i usunie dokładnie określony obiekt.
2
Nie działa dla mnie: nie można przesłać widoku na fragment. obiekt tutaj jest fragmentem.
FRK
przeglądarka musi być statyczna?
Kanagalingam
10

ViewPager próbuje robić sprytne rzeczy związane z ponownym wykorzystaniem przedmiotów, ale wymaga zwrócenia nowych pozycji przedmiotów, gdy sytuacja się zmieni. Spróbuj dodać to do swojego PagerAdapter:

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

Zasadniczo mówi ViewPager, że wszystko się zmieniło (i zmusza go do ponownego utworzenia instancji). To jedyna rzecz, o której przychodzi mi do głowy.

Chris Banes
źródło
1
Tak, czytałem o tej opcji w tym wątku: stackoverflow.com/questions/7263291/… - jednak wydaje się, że jest to podejście młotem kowalskim. Z pewnością musi istnieć bardziej elegancki sposób? Zastanawiam się, czy jest to związane z używaniem list jako stron pagera?
Matthias
To trochę podejście młotowe, ale możesz z tego cofnąć się. tj. zwraca poprawną wartość z metody.
Chris Banes,
@ChrisBanes Jak powiedziałeś, dzwoniąc return POSITION_NONE;, forces it to re-instantiate everythingale w moim przypadku nie chcę ponownie tworzyć instancji wszystkiego , więc czy byłoby możliwe usunięcie viewbez czyszczenia istniejących rzeczy? Daj mi znać
Ritesh Adulkar
jesteś smakiem życia
maska8
2

Próbowałem zbyt wielu rozwiązań, ale nieoczekiwanie viewPager.post()zadziałało

 mAdapter = new NewsVPAdapter(getContext(), articles);
    viewPager.post(new Runnable() {
        @Override
        public void run() {
            viewPager.setAdapter(mAdapter);
        }
    });
Zeero0
źródło
1
mój Boże. dlaczego nikt tego nie głosował? Dziwnie się zachowywałem, gdy ponownie wchodziłem do fragmentu, a znajdujący się w nim plik podglądu nie renderował się, a opóźnienie tego trochę w ten sposób rozwiązało problem!
Fugogugo
0

Biblioteka obsługi systemu Android ma działanie demonstracyjne, które zawiera ViewPager z ListView na każdej stronie. Powinieneś prawdopodobnie spojrzeć i zobaczyć, co robi.

W Eclipse (z Android Dev Tools r20):

  1. Wybierz New > Android Sample Project
  2. Wybierz docelowy poziom API (sugeruję najnowszy dostępny)
  3. Wybierz Support4Demos
  4. Kliknij projekt prawym przyciskiem myszy i wybierz Android Tools > Add Support Library
  5. Uruchom aplikację i wybierz, Fragmenta następniePager

Kod do tego znajduje się w src/com.example.android.supportv4.app/FragmentPagerSupport.java. Powodzenia!

iskrzący
źródło
dzięki - przyjrzę się temu! Może zauważę coś, co robimy nie tak.
Matthias
0

Wpadłem na to i miałem bardzo podobne problemy. Zapytałem nawet o przepełnienie stosu.

Dla mnie, w rodzicu rodzica mojego zdania, ktoś podklasował LinearLayouti przejmował requestLayout()bez dzwonienia super.requestLayout(). Zapobiegło to wywołaniu onMeasurei onLayoutwywołaniu na moim ViewPager (chociaż hierarchyviewer ręcznie je wywołuje). Bez pomiaru będą wyświetlane jako puste w ViewPager.

Sprawdź więc swoje widoki zawierające. Upewnij się, że podklasy są z View i nie przesłaniają ślepo requestLayout ani niczego podobnego.

Tim O'Brien
źródło
0

Miałem ten sam problem, który ma coś wspólnego ListView(ponieważ mój pusty widok pojawia się dobrze, jeśli lista jest pusta). Właśnie zadzwoniłem requestLayout()do problemu ListView. Teraz rysuje się dobrze!

Oleg Vaskevich
źródło
0

Napotkałem ten sam problem podczas korzystania z ViewPager i FragmentStatePagerAdapter. Próbowałem użyć programu obsługi z 3-sekundowym opóźnieniem, aby wywołać unieważnienie () i requestLayout (), ale to nie zadziałało. Co zadziałało, to zresetowanie koloru tła viewPagera w następujący sposób:

MyFragment.java

    private Handler mHandler;
    private Runnable mBugUpdater;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View rootView = new ViewPager(getActivity());
        //...Create your adapter and set it here...

        mHandler = new Handler();
        mBugUpdater = new Runnable(){
            @Override
            public void run() {
                mVp.setBackgroundColor(mItem.getBackgroundColor());
                mHandler = null;
                mBugUpdater = null;
            }           
        };
        mHandler.postDelayed(mBugUpdater,50);

        return rootView;
    }

    @Override
    public void onPause() {
        if(mHandler != null){
            //Remove the callback if it hasn't triggered yet
            mHandler.removeCallbacks(mBugUpdater);
            mHandler = null;
            mBugUpdater = null;
        }
        super.onPause();
     }
Chris Sprague
źródło
0

Miałem problem z tymi samymi objawami, ale z inną przyczyną, która okazała się głupim błędem z mojej strony. Pomyślałem, że dodam to tutaj na wypadek, gdyby to komuś pomogło.

Miałem ViewPagera używającego FragmentStatePagerAdapter, który miał dwa fragmenty, ale później dodałem trzeci. Zapomniałem jednak, że domyślny limit strony poza ekranem to 1 - więc gdy przełączyłem się na nowy trzeci fragment, pierwszy zostałby zniszczony, a następnie odtworzony po przełączeniu z powrotem. Problem polegał na tym, że moja aktywność była odpowiedzialna za powiadamianie tych fragmentów o zainicjowaniu ich stanu interfejsu użytkownika. Zdarzyło się to zadziałać, gdy cykle życia aktywności i fragmentów były takie same, ale aby to naprawić, musiałem zmienić fragmenty, aby zainicjować ich własny interfejs użytkownika podczas ich cyklu życia uruchamiania. Na koniec zmieniłem również setOffscreenPageLimit na 2, aby wszystkie trzy fragmenty były zawsze żywe (bezpieczne w tym przypadku, ponieważ nie wymagały dużej ilości pamięci).

dfinn
źródło
-1

Miałem podobny problem. Buforuję widoki, ponieważ potrzebuję tylko 3 widoków w formacie ViewPager. Kiedy przesuwam się do przodu, wszystko jest w porządku, ale kiedy zaczynam się przesuwać do tyłu, pojawia się błąd, mówi się, że „mój widok ma już rodzica”. Rozwiązaniem jest ręczne usuwanie niepotrzebnych elementów.

@Override
    public Object instantiateItem(ViewGroup container, int position) {
        int localPos = position % SIZE;
        TouchImageView view;
        if (touchImageViews[localPos] != null) {
            view = touchImageViews[localPos];
        } else {
            view = new TouchImageView(container.getContext());
            view.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
            touchImageViews[localPos] = view;
        }
        view.setImageDrawable(mDataModel.getPhoto(position));
        Log.i(IRViewPagerAdpt.class.toString(), "Add view " + view.toString() + " at pos: " + position + " " + localPos);
        if (view.getParent() == null) {
        ((ViewPager) container).addView(view);
    }
        return view;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object view) {
        //      ((ViewPager) container).removeView((View) view);
        Log.i(IRViewPagerAdpt.class.toString(), "remove view " + view.toString() + " at pos: " + position);
    }

..................

private static final int SIZE = 3;
private TouchImageView[] touchImageViews = new TouchImageView[SIZE];
Roman Nazarevych
źródło
-1

Dla mnie problem polegał na powrocie do działania po zabiciu procesu aplikacji. Używam niestandardowego adaptera pagera widoku zmodyfikowanego ze źródeł systemu Android. Pager widoku jest osadzony bezpośrednio w działaniu.

Powołanie viewPager.setCurrentItem(position, true);

(z animacją) po ustawieniu danych i notifyDataSetChanged () wydaje się działać, ale jeśli parametr jest ustawiony na false, nie działa i fragment jest pusty. To skrajny przypadek, który może komuś pomóc.

Meanman
źródło