Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
264 views
in Technique[技术] by (71.8m points)

android - reorder pages in FragmentStatePagerAdapter using getItemPosition(Object object)

I believe that FragmentStatePagerAdapter does not behave correctly when overriding getItemPosition(Object object) with the purpose of reordering the pages.

Below is a simple example. In the initial state, the order of the pages is {A, B, C}. Upon calling toggleState(), the order of the pages changes to {A, C, B}. By overriding getItemPosition(Object object), we ensure that the current page being viewed (A, B, or C) does not change.

public static class TestPagerAdapter extends FragmentStatePagerAdapter {
    private boolean mState = true;

    public TestPagerAdapter(FragmentManager fragmentManager) {
        super(fragmentManager);
    }

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

    private void toggleState() {
        mState = !mState;
        notifyDataSetChanged();
    }

    private String getLabel(int position) {
        switch (position) {
            case 0:
                return "A";
            case 1:
                return mState ? "B" : "C";
            default:
                return mState ? "C" : "B";
        }
    }

    @Override
    public int getItemPosition(Object object) {
        String label = ((TestFragment) object).getLabel();
        if (label.equals("A")) {
            return 0;
        } else if (label.equals("B")) {
            return mState ? 1 : 2;
        } else {
            return mState ? 2 : 1;
        }
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return getLabel(position);
    }

    @Override
    public Fragment getItem(int position) {
        return TestFragment.newInstance(getLabel(position));
    }
}

I have encountered two separate behaviours which seem incorrect.

  1. If I immediately call toggleState() (while viewing page A, before swiping to any other page), the app crashes.

    java.lang.IndexOutOfBoundsException: Invalid index 2, size is 2
      at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:251)
      at java.util.ArrayList.set(ArrayList.java:477)
      at android.support.v4.app.FragmentStatePagerAdapter.destroyItem(FragmentStatePagerAdapter.java:136)
      at android.support.v4.view.ViewPager.populate(ViewPager.java:867)
      at android.support.v4.view.ViewPager.setCurrentItemInternal(ViewPager.java:469)
      at android.support.v4.view.ViewPager.setCurrentItemInternal(ViewPager.java:441)
      at android.support.v4.view.ViewPager.dataSetChanged(ViewPager.java:766)
      at android.support.v4.view.ViewPager$PagerObserver.onChanged(ViewPager.java:2519)
      at android.database.DataSetObservable.notifyChanged(DataSetObservable.java:37)
      at android.support.v4.view.PagerAdapter.notifyDataSetChanged(PagerAdapter.java:276)
      at com.ugglynoodle.test.testfragmentstatepageradapter.MainActivity$TestPagerAdapter.toggleState(MainActivity.java:55)
      ...
    

    Looking at the source of FragmentStatePagerAdapter, this would be fixed by first checking the size of mFragments (as in lines 113-115) before calling set() in line 136.

  2. If I first swipe to page B, then getItem(2) is called, page C is created, and mFragments now has a size of 3 (this will prevent the crash above from happening in a moment). Then I swipe back to page A, and page C is destroyed, as it should be (since it is 2 pages away, and I'm using the default offscreen page limit of 1). Now, I call toggleState(). Page B is now destroyed. However, page C is NOT recreated! This means, when I now swipe to the right, I get an empty page.

First, it would be nice to know whether I'm correct and these are in fact bugs, or whether I'm doing something wrong. If they are bugs, can anyone suggest a workaround (other than debugging and rebuilding the support library myself)? Surely somebody must have overridden getItemPosition(Object object) successfully (apart from setting everything to POSITION_NONE)?

I am using the current revision (10) of the support library.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Looking at the source of FragmentStatePagerAdapter, I figured out exactly what is going wrong. The FragmentStatePagerAdapter caches the fragments and saved states in ArrayLists: mFragments and mSavedState. But when the fragments are reordered, there's no mechanism for reordering the elements of mFragments and mSavedState. Therefore, the adapter will provide the wrong fragments to the pager.

I've filed an issue for this, and attached a fixed implementation (NewFragmentStatePagerAdapter.java) to the issue. In the fix, I've added a getItemId() function to FragmentStatePagerAdapter. (This mirrors the reordering implementation in FragmentPagerAdapter.) An array of the itemIds by adapter position is stored at all times. Then, in notifyDataSetChanged(), the adapter checks if the itemIds array has changed. If it has, then mFragments and mSavedState are reordered accordingly. Further modifications can be found in destroyItem(), saveState() and restoreState().

To use this class, getItemPosition() and getItemId() must be implemented consistently with getItem().


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...