Question

I'm using a ViewPager that contains several ListViews, with code similar to that in the answer for Infinite ViewPager. The idea is to have something like the day view for the Google Calendar app (whose source seems to be unavailable; only the default calendar app's is but it uses a ViewSwitcher) - I want to make it seem like the user can swipe infinitely left and right, but there are actually only 3 items in the ViewPager, and when the user hits page 0 or 2, we set 1 as the current page and update accordingly.

Now, this all works. However, strangely, when the phone is rotated and the activity is rebuilt (I'm avoiding using configChanges for now), the pages in the app are instantiated again, but out of order. Instead of 0->1->2, the order is 1->0->2, and this screws up the order of the pages in the app.

My Fragment, in onActivityCreated():

mPagerAdapter = new ContinuousPagerAdapter(R.layout.my_listview, this);

// set the adapter
mViewPager = (ViewPager) getView().findViewById(R.id.agendaViewPager);
mViewPager.setOffscreenPageLimit(3);
mViewPager.setOnPageChangeListener(this);
mViewPager.setAdapter(mPagerAdapter);
mViewPager.setSaveEnabled(false);
// ...
mViewPager.setCurrentItem(1, false);
loadData();

The Pager Adapter:

import android.content.Context;
import android.os.Parcelable;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;

public class ContinuousPagerAdapter extends PagerAdapter {

    OnPageInstantiatedListener pListener;
    ViewPager container;
    int childLayoutResId;

    @SuppressWarnings("unused")
    private ContinuousPagerAdapter() {
    }

    /**
     * @param childLayoutResId Layout resource ID of the children to be inflated
     */
    public ContinuousPagerAdapter(int childLayoutResId, OnPageInstantiatedListener pListener) {
        this.childLayoutResId = childLayoutResId;
        this.pListener = pListener;
    }

    @Override
    public int getCount() {
        return 3;
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return view == object;
    }

    @Override
    public Object instantiateItem(View container, int position) {
        this.container = (ViewPager) container;

        // inflate a new child view
        LayoutInflater li = (LayoutInflater) container.getContext().getSystemService(
                Context.LAYOUT_INFLATER_SERVICE);
        View childView = li.inflate(childLayoutResId, null, false);

        // add it to the view pager and return
        int count = this.container.getChildCount();
        int actualPos = count > position ? position : count;
        this.container.addView(childView, actualPos);
        pListener.onPageInstantiated(actualPos); // sometimes use 0 instead of actualPos, with different but still inconsistent results
        return childView;
    }

    @Override
    public void destroyItem(View container, int position, Object object) {
        ((ViewPager) container).removeViewAt(position);
    }

    public static interface OnPageInstantiatedListener {
        public void onPageInstantiated(int position);
    }

    /**
     * Needed to ensure all the items are instantiated
     */
    @Override
    public int getItemPosition(Object object) {
        return POSITION_NONE;
    }

    @Override
    public Parcelable saveState() {
        return null;
    }

    @Override
    public void restoreState(Parcelable state, ClassLoader loader) {
    }

    @Override
    public void finishUpdate(View container) {
    }
}

I don't understand why the pages are instantiated in the 1->0->2 order after rotation. I'm not saving state either. Any insights into this would be helpful.

Was it helpful?

Solution

After meticulously debugging the app and looking through the ViewPager source, I found the problem.

The first time the app starts and mViewPager.setAdapter(mPagerAdapter) is called, the pages are instantly initiated and the app works as it should. However, when the phone is rotated, calling setAdapter() postpones instantiating the pages because getWindowToken() returns null as the window is not ready yet. Instantiating is delayed until even after onResume() is called in some loop.

Calling setCurrentItem(1, false) makes the first page the primary page, and as a result it is instantiated before the other pages, resulting in the at-first strange 1->0->2 instantiation.

The solution? Use a Handler to run the setCurrentItem() and load data after the other pages have been instantiated:

new Handler().post(new Runnable() {
    @Override
    public void run() {
        mViewPager.setCurrentItem(1, false);
        cleanupAndShowData();
    }
});

Though I'd normally want to avoid using Handlers, it seems this is the only option I've found thus far, because the pages themselves are added in a looper.

EDIT: Even the above had some issues. I ended up calling mViewPager.setCurrentItem(1, false) only after all pages have been instantiated (in onPageInstantiated()).

OTHER TIPS

actualPos= position%getCount();

If this doesn't help, please give some additional code.

I would also suggest a little modification

mViewPager.setOffscreenPageLimit(2);   //use 2 instead of 3 unless neccessary
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top