Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Viewpager Adapter getItem always called for index 0 and 1

I was just wondering if it is the normal behaviour of viewpager and its adapter to always call the getItem() method for index 0 and 1, even if I immediately set a current position.

Here is my code:

mNewsPagerAdapter = new NewsDetailPagerAdapter(getChildFragmentManager());
mNewsPagerAdapter.updateNewsList(news);

mViewPager = (ViewPager) mView.findViewById(R.id.horizontal_view_pager);
mViewPager.setPageMargin(2);
mViewPager.setPageMarginDrawable(R.color.black);

mViewPager.setAdapter(mNewsPagerAdapter);
mViewPager.setCurrentItem(mCurrentPositionPager, false);

If I switch from my overview activity to my detail activity with this viewpager, the adapter always calls the getItem() method for position 0 and 1 and after that the getItem() method for the position of mOriginalPosition and its neighbors. I was wondering if this is the correct behaviour or if I missed something to implement it in a right way. Thanks for your help :)

Edit: Added my adapter code

public class NewsDetailPagerAdapter extends FragmentStatePagerAdapter {

private SparseArray<Fragment> mPageReferenceMap = new SparseArray<Fragment>();

private ArrayList<News> mNewsList;

public NewsDetailPagerAdapter(FragmentManager fm) {
    super(fm);
}

/**
 * Setzt die neuen News.
 **/
public void updateNewsList(ArrayList<News> list) {
    mNewsList = list;
}

@Override
public Fragment getItem(int position) {
    Log.d("debug", "getItem position:" + position);
    News newsItem = mNewsList.get(position);


    NavigationFragment fragment = new NavigationFragment();

    mPageReferenceMap.put(position, fragment);

    return fragment;
}


@Override
public int getCount() {
    return mNewsList.size();
}

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

public Fragment getFragment(int position) {
    return mPageReferenceMap.get(position);
}

}

like image 257
jennymo Avatar asked Sep 02 '15 09:09

jennymo


3 Answers

It is normal (and intelligent in my opinion). ViewPager class has one property named mOffscreenPageLimit with default value of 1. This number determines how many pages on the left and on the right of the current page that the Viewpager will preload. For instance, you have 10 pages, current position is 5 and mOffcreenPageLimit is 1, the page at position 4 and 6 will be loaded.

You could change this property by calling this method

viewpager. setOffscreenPageLimit(int)

If you pass in an integer that is smaller than 1, it has no effect.

like image 96
Tin Tran Avatar answered Nov 16 '22 03:11

Tin Tran


Yes, this is the normal behaviour of the ViewPager, because it will always try to stay ahead of the user by rendering tabs that limit with the drawing area. I personally don't recommend creating a custom ViewPager as you are almost sure to break functionality unless you really know what you are doing. Your adapter class should look something like this:

public class YourCustomPagerAdapter extends FragmentStatePagerAdapter {
    private List<Fragment> fragmentList = new ArrayList<>();
    private List<String> titleList = new ArrayList<>();

    public WizardPagerAdapter(FragmentManager fm) {
        super(fm);
    }

    public void addFragment(Fragment fragment, String title) {
        fragmentList.add(fragment);
        titleList.add(title);
    }

    @Override
    public Fragment getItem(int position) {
        return fragmentList.get(position);
    }

    @Override
    public CharSequence getPageTitle(int position) {
        super.getPageTitle(position);
        return titleList.get(position);
    }

    @Override
    public int getCount() {
        return fragmentList.size();
    }
}

and you should add your fragments as such:

 @Override
 public void onCreate(Bundle savedInstanceState) {
     ...
     YourCustomPagerAdapter adapter = new YourCustomPagerAdapter (getSupportFragmentManager());
     adapter.addFragment(FragmentOne.newInstance(), "Frag 1");
     adapter.addFragment(FragmentTwo.newInstance(), "Frag 2");
     viewPager.setAdapter(adapter);
     ...
 }
like image 34
Chisko Avatar answered Nov 16 '22 02:11

Chisko


Actually this is the normale behavior. In fact, as soos as you associate the ViewPager with the adapter, the adapter creates the first visibile layout (index 0) end the next one (index 1). This is done by default in the "setAdapter". Then, when you set a different position, the adapter will instantiate the fragment at the selected index, the previous one and the next one.

This is the usual ViewPager setAdapter code:

public void setAdapter(PagerAdapter adapter) {
    if (mAdapter != null) {
        mAdapter.setViewPagerObserver(null);
        mAdapter.startUpdate(this);
        for (int i = 0; i < mItems.size(); i++) {
            final ItemInfo ii = mItems.get(i);
            mAdapter.destroyItem(this, ii.position, ii.object);
        }
        mAdapter.finishUpdate(this);
        mItems.clear();
        removeNonDecorViews();
        mCurItem = 0;
        scrollTo(0, 0);
    }

    final PagerAdapter oldAdapter = mAdapter;
    mAdapter = adapter;
    mExpectedAdapterCount = 0;

    if (mAdapter != null) {
        if (mObserver == null) {
            mObserver = new PagerObserver();
        }
        mAdapter.setViewPagerObserver(mObserver);
        mPopulatePending = false;
        final boolean wasFirstLayout = mFirstLayout;
        mFirstLayout = true;
        mExpectedAdapterCount = mAdapter.getCount();
        if (mRestoredCurItem >= 0) {
            mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);
            setCurrentItemInternal(mRestoredCurItem, false, true);
            mRestoredCurItem = -1;
            mRestoredAdapterState = null;
            mRestoredClassLoader = null;
        } else if (!wasFirstLayout) {
            populate();
        } else {
            requestLayout();
        }
    }

    if (mAdapterChangeListener != null && oldAdapter != adapter) {
        mAdapterChangeListener.onAdapterChanged(oldAdapter, adapter);
    }
}

In order to change the ViewPager behavior, you could extend the classic ViewPager overriding the setAdapter method and set the mCurrItem to the desired position.

I hope it helped

Edit:

After different tests, we found a solution.

If the ViewPager adapter is set after ViewPager layout become visible, items 0 and 1 are load. If you want to avoid this behavior but you can't set the adapter before the layout become visible (because you are waiting for data), than you can use this workaround:

1) Set the ViewPager visibility initially to GONE

2) After you receive all the data, you update the adapter and you set the current item value

3) Finally you set the ViewPager visibility to VISIBLE

Here you can find an example:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {

    View v = inflater.inflate(R.layout.detail_overview_fragment, container, false);

    final int position = getArguments().getInt("position");
    final ViewPager viewPager = (ViewPager) v.findViewById(R.id.viewpager);
    viewPager.setVisibility(View.GONE);

    Handler handler = new Handler();
    handler.postDelayed(new Runnable() {
        @Override
        public void run() {
            viewPager.setAdapter(new PagerAdapter(getChildFragmentManager()));
            viewPager.setCurrentItem(position);
            viewPager.setVisibility(View.VISIBLE);

        }
    },5000);

    return v;
}
like image 37
Federico De Gioannini Avatar answered Nov 16 '22 03:11

Federico De Gioannini