Mam problem z zniszczeniem (usunięciem) jednej strony z ViewPager
po zmianie orientacji ekranu. Spróbuję opisać problem w poniższych linijkach.Destroy element z adaptera ViewPager po zmianie orientacji ekranu
Używam FragmentStatePagerAdapter
dla adaptera ViewPager
i małego interfejsu opisującego sposób działania niedziałającego pagera widoku. Ideą tego jest to, że możesz przewijać w prawo, aż dotrzesz do końca ViewPager
. Jeśli możesz załadować więcej wyników z połączenia API, strona postępu jest wyświetlana do momentu pojawienia się wyników.
Wszystko dobrze, aż tutaj, teraz pojawia się problem. Jeśli w trakcie tego procesu ładowania, obrócić ekran (to nie będzie miało wpływu na wywołanie API, które jest w zasadzie AsyncTask
), kiedy wraca połączenia, awarii aplikacji daje mi ten wyjątek:
E/AndroidRuntime(13471): java.lang.IllegalStateException: Fragment ProgressFragment{42b08548} is not currently in the FragmentManager
E/AndroidRuntime(13471): at android.support.v4.app.FragmentManagerImpl.saveFragmentInstanceState(FragmentManager.java:573)
E/AndroidRuntime(13471): at android.support.v4.app.FragmentStatePagerAdapter.destroyItem(FragmentStatePagerAdapter.java:136)
E/AndroidRuntime(13471): at mypackage.OutterFragment$PagedSingleDataAdapter.destroyItem(OutterFragment.java:609)
Po wykopaniu trochę w kod biblioteki wydaje się, że pole danych tego fragmentu jest w tym przypadku mniejsze niż 0
, co podnosi ten wyjątek.
Oto kod adaptera pagera:
static class PagedSingleDataAdapter extends FragmentStatePagerAdapter implements
IEndlessPagerAdapter {
private WeakReference<OutterFragment> fragment;
private List<DataItem> data;
private SparseArray<WeakReference<Fragment>> currentFragments = new SparseArray<WeakReference<Fragment>>();
private ProgressFragment progressElement;
private boolean isLoadingData;
public PagedSingleDataAdapter(SherlockFragment fragment, List<DataItem> data) {
super(fragment.getChildFragmentManager());
this.fragment = new WeakReference<OutterFragment>(
(OutterFragment) fragment);
this.data = data;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
Object item = super.instantiateItem(container, position);
currentFragments.append(position, new WeakReference<Fragment>(
(Fragment) item));
return item;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
currentFragments.put(position, null);
super.destroyItem(container, position, object);
}
@Override
public Fragment getItem(int position) {
if (isPositionOfProgressElement(position)) {
return getProgessElement();
}
WeakReference<Fragment> fragmentRef = currentFragments.get(position);
if (fragmentRef == null) {
return PageFragment.newInstance(args); // here I'm putting some info
// in the args, just deleted
// them now, not important
}
return fragmentRef.get();
}
@Override
public int getCount() {
int size = data.size();
return isLoadingData ? ++size : size;
}
@Override
public int getItemPosition(Object item) {
if (item.equals(progressElement) && !isLoadingData) {
return PagerAdapter.POSITION_NONE;
}
return PagerAdapter.POSITION_UNCHANGED;
}
public void setData(List<DataItem> data) {
this.data = data;
notifyDataSetChanged();
}
@Override
public boolean isPositionOfProgressElement(int position) {
return isLoadingData && position == data.size();
}
@Override
public void setLoadingData(boolean isLoadingData) {
this.isLoadingData = isLoadingData;
}
@Override
public boolean isLoadingData() {
return isLoadingData;
}
@Override
public Fragment getProgessElement() {
if (progressElement == null) {
progressElement = new ProgressFragment();
}
return progressElement;
}
public static class ProgressFragment extends SherlockFragment {
public ProgressFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
TextView progressView = new TextView(container.getContext());
progressView.setGravity(Gravity.CENTER_HORIZONTAL
| Gravity.CENTER_VERTICAL);
progressView.setText(R.string.loading_more_data);
LayoutParams params = new LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.FILL_PARENT);
progressView.setLayoutParams(params);
return progressView;
}
}
}
onPageSelected()
zwrotna poniżej, które zasadniczo rozpoczyna wywołanie API czy potrzebne:
@Override
public void onPageSelected(int currentPosition) {
updatePagerIndicator(currentPosition);
activity.invalidateOptionsMenu();
if (requestNextApiPage(currentPosition)) {
pagerAdapter.setLoadingData(true);
requestNextPageController.requestNextPageOfData(this);
}
Teraz warto też powiedzieć, co wywołanie API wykonuje się po dostarczeniu wyników. Oto zwrotna:
@Override
public boolean onTaskSuccess(Context arg0, List<DataItem> result) {
data = result;
pagerAdapter.setLoadingData(false);
pagerAdapter.setData(result);
activity.invalidateOptionsMenu();
return true;
}
Ok, teraz, ponieważ metoda setData()
wywołuje notifiyDataSetChanged()
, to wywoła getItemPosition()
do fragmentów, które są aktualnie w tablicy currentFragments
. Oczywiście dla elementu postępu zwraca on POSITION_NONE
, ponieważ chcę usunąć tę stronę, więc to zasadniczo wywołuje wywołanie zwrotne od destroyItem()
z . Jeśli nie obracam ekranu, wszystko działa poprawnie, ale jak już powiedziałem, jeśli obracam go, gdy wyświetlany jest element postępu, a wywołanie API jeszcze się nie zakończyło, wywołanie zwrotne destroyItem()
zostanie wywołane po ponownym uruchomieniu działania .
Może powinienem również powiedzieć, że hostuję ViewPager
w innym Fragmentie, a nie w działaniu, więc OutterFragment
jest hostem ViewPager
. Ja uruchamianiu tego pagerAdapter
w onActivityCreated()
zwrotnego z OutterFragment
i używając setRetainInstance(true)
tak, że gdy ekran obraca pagerAdapter
pozostaje taka sama (nie powinny być zmieniane, prawda?), Kod tutaj:
if (pagerAdapter == null) {
pagerAdapter = new PagedSingleDataAdapter(this, data);
}
pager.setAdapter(pagerAdapter);
if (savedInstanceState == null) {
pager.setOnPageChangeListener(this);
pager.setCurrentItem(currentPosition);
}
Podsumowując teraz The PROBLEM jest:
gdy próbuję usunąć element postępu od ViewPager
po jego wystąpienia oraz działalność została zniszczona i odtworzone (orientacja ekranu zmienione) otrzymuję powyższy wyjątek (the pagerAdapter
pozostaje taka sama, więc wszystko w środku Pozostają takie same, odniesienia itp. ... ponieważ OutterFragment
, który jest hostem pagerAdapter
, nie jest zniszczony, jest oderwany od aktywności, a następnie ponownie dołączony). Prawdopodobnie dzieje się coś z menedżerem fragmentów, ale naprawdę nie wiem co.
Co ja już próbowałem:
Próbując usunąć fragment mojego postępu przy użyciu innej techniki czyli na
onTaskSuccess()
zwrotnego starałem się usunąć fragment z menedżera fragmentu, nie działa .Próbowałem również ukryć element postępu, zamiast go całkowicie usunąć z menedżera fragmentów. To działało 50%, ponieważ widoku już nie było, ale miałem pustą stronę, więc nie jest to tak naprawdę to, czego szukam.
Próbowałem również (ponownie) dołączyć
progressFragment
do menedżera fragmentów po zmianie orientacji ekranu, to też nie zadziałało.Próbowałem również usunąć, a następnie dodać ponownie fragment postępu do menedżera fragmentów po odtworzeniu działania, nie działał.
Próbowałem wywołać
destroyItem()
ręcznie z wywołania zwrotnegoonTaskSuccess()
(co jest naprawdę, naprawdę brzydkie), ale nie działało.
Przykro mi z powodu tak długiego postu, ale starałem się wyjaśnić problem najlepiej jak potrafię, abyście mogli go zrozumieć.
Każde rozwiązanie, zalecenie jest bardzo doceniane.
Dzięki!
AKTUALIZACJA: ZNALEZIONO ROZWIĄZANIE OK, więc zajęło to trochę czasu. Problem polegał na tym, że wywołanie zwrotne destroyItem() zostało wywołane dwukrotnie w fragmencie postępu, jeden raz, gdy zmieniła się orientacja ekranu, a następnie ponownie po zakończeniu wywołania api. Właśnie dlatego wyjątek. Rozwiązanie, które znalazłem, jest następujące: Śledzenie, czy api api się zakończyło, czy nie, i zniszcz fragment postępu właśnie w tym przypadku, kod poniżej.
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (object.equals(progressElement) && apiCallFinished == true) {
apiCallFinished = false;
currentFragments.put(position, currentFragments.get(position + 1));
super.destroyItem(container, position, object);
} else if (!(object.equals(progressElement))) {
currentFragments.put(position, null);
super.destroyItem(container, position, object);
}
}
a następnie ten apiCallFinished jest ustawiona na false w konstruktora adaptera i prawdziwe w onTaskSuccess()
zwrotnego. I to naprawdę działa!
Może to głupie pytanie, ale czy po obróceniu ekranu ponownie dodajesz swoje fragmenty do widoku ViewPager? Twoja aktywność zostaje zniszczona podczas zmiany orientacji. –
ah, dobry, tak, dodaję je, ale zapomniałem dodać kod do niego :). Dzięki za wskazanie tego, trochę edytuję swój post. Twoje zdrowie! –
Spróbuj ponownie dodać karty z .addTab() w onCreate. Czy Twój problem dotyczy tylko funkcji progressFragment? –