137

I have a ViewPager hooked up to a FragmentPagerAdapter that's displaying three fragments. The ViewPager appears to destroy a hosted fragment's view when it is more than one swipe away from the current position.

These views are all simple lists and this optimization is completely unnecessary, so I'd like to disable it. It's causing some visual problems because the lists have layout animations applied to them and those animations are being replayed after they've been destroyed and recreated. It also shows the scrollbar intro animation each time (where the scrollbar is briefly visible to indicate that scrolling is possible) which can be distracting, and the user's current scroll position is lost in the process.

It also doesn't load the third fragment until the first swipe happens, which is problematic because each fragment handles its own service calls and I'd prefer to have all three fire off at the same time when the activity loads. Having the third service call delayed is less than ideal.

Is there any way to convince ViewPager to stop this behavior and just keep all my fragments in memory?

chefgon
  • 1,931
  • 4
  • 17
  • 17

4 Answers4

341

In revision 4 of the Support Package, a method was added to ViewPager which allows you to specify the number of offscreen pages to use, rather than the default which is 1.

In your case, you want to specify 2, so that when you are on the third page, the first one is not destroyed, and vice-versa.

mViewPager = (ViewPager)findViewById(R.id.pager);
mViewPager.setOffscreenPageLimit(2);
David Snabel-Caunt
  • 57,804
  • 13
  • 114
  • 132
  • any idea how to do this on a coverflow? – josephus Aug 16 '12 at 06:53
  • 4
    Hello. In what I am working on, the fragments/pages gets created dynamically so there is indefinite number of probable fragments. This case, it usually goes to 10 or less. Would it not be poor use of memory to use this solution to that much pages? The fragments will only hold the view, by the way. Thanks! – mahkie Aug 24 '12 at 00:13
  • This method throws : "java.lang.IllegalStateException: Fragment already added:" error for my app. – alicanbatur Feb 18 '14 at 14:11
  • @mahie the above solutions if for This is offered as an optimization. If you know in advance the number * of pages you will need to support or have lazy-loading mechanisms in place * on your pages, tweaking this setting can have benefits in perceived smoothness * of paging animations and interaction. If you have a small number of pages (3-4) * that you can keep active all at once, less time will be spent in layout for * newly created view subtrees as the user pages back and forth – Asthme Jan 14 '16 at 15:27
9

By default, ViewPager recreates the fragments when you swipe the page. To prevent this, you can try one of two things:

1. In the onCreate() of your fragments, call setRetainInstance(true).

2. If the number of fragments is fixed & relatively small, then in your onCreate() add the following code:

ViewPager mViewPager = (ViewPager) findViewById(R.id.pager);
mViewPager.setOffscreenPageLimit(3);

If I remember correctly, the second option is more promising. But I urge you to try both and see which of them work.

Pankaj Talaviya
  • 3,328
  • 28
  • 31
3

"Set the number of pages that should be retained to either side of the current page in the view hierarchy in an idle state. Pages beyond this limit will be recreated from the adapter when needed."

http://developer.android.com/reference/android/support/v4/view/ViewPager.html#setOffscreenPageLimit(int)

Percy Vega
  • 1,449
  • 1
  • 16
  • 13
2

The selected answer is good but wasn't really good for me. This is because I had a lot of fragments (20-30) and so the activity with the ViewPager would take a lot of time to load if I had used setOffscreenPageLimit(30). In my case I had to implement the destroyItem() method (in the ViewPager's Adapter class) and remove the call to the super function. Here's the pseudo code

@Override                                                                                    
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
                                                                                             
}                                                                                            

Now if you want more context here is the entire ViewPagerAdapter class

public class ViewPagerAdapter extends FragmentPagerAdapter {
    private final List<Fragment> listFragment =
            new ArrayList<>();
    public ViewPagerAdapter(@NonNull FragmentManager fm) {
        super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
    }

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

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

    /*This is the method I was taking about*/
    @Override
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
    
    }

    public void addFragment(Fragment fragment)
    {
        listFragment.add(fragment);
        notifyDataSetChanged();
    }
}
Joel
  • 353
  • 2
  • 7