101
votes

The fragments I use in my ViewPager instance are quite resource intensive, so I'd only like to load one at a time. When I try the following:

mViewPager.setOffscreenPageLimit(0);
mViewPager.setAdapter(mPagerAdapter);

My FragmentStatePagerAdapter.getItem(int position) override function is called 3 times, which is what happens when I call mViewPager.setOffscreenPageLimit(1). I would expect it to only be called once, because I specified 0 offscreen pages.

I believe I'm calling everything correctly, because if I call mViewPager.setOffscreenPageLimit(2), FragmentStatePagerAdapter.getItem(int position) is called 5 times as I would expect.

Does ViewPager require a minimum of 1 offscreen pages, or am I doing something wrong here?

13
I've added a feature request: code.google.com/p/android/issues/detail?id=56667Mark
I had only 3 tabs and settingViewPager.setOffscreenPageLimit(2) worked for me. Try ViewPager.setOffscreenPageLimit(totTabs - 1)sibin

13 Answers

115
votes

Does ViewPager require a minimum of 1 offscreen pages

Yes. If I am reading the source code correctly, you should be getting a warning about this in LogCat, something like:

Requested offscreen page limit 0 too small; defaulting to 1
125
votes

The best way that I found was setUserVisibleHint
add this to your fragment

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    if (isVisibleToUser) {
        // load data here
    }else{
       // fragment is no longer visible
    }
}
8
votes

You can try like this :

public abstract class LazyFragment extends Fragment {
    protected boolean isVisible;
    /**
     * 在这里实现Fragment数据的缓加载.
     * @param isVisibleToUser
     */
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if(getUserVisibleHint()) {
            isVisible = true;
            onVisible();
        } else {
            isVisible = false;
            onInvisible();
        }
    }
    protected void onVisible(){
        lazyLoad();
    }
    protected abstract void lazyLoad();
    protected void onInvisible(){}
protected abstract void lazyLoad();
protected void onInvisible(){}
7
votes

First Add

   boolean isFragmentLoaded = false;

than

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    if (isVisibleToUser && !isFragmentLoaded) {
        //Load Your Data Here like.... new GetContacts().execute();

        isFragmentLoaded = true;
    }
else{
     }
}
2
votes

this may be old thread but this seems to work for me. Override this function :

@Override
public void setMenuVisibility(boolean menuVisible) { 
    super.setMenuVisibility(menuVisible);

    if ( menuVisible ) {
        /**
         * Load your stuffs here.
         */
    } else  { 
        /**
         * Fragment not currently Visible.
         */
    } 
}

happy codings...

2
votes

ViewPager is default to load the next page(Fragment) which you can't change by setOffscreenPageLimit(0). But you can do something to hack. You can implement onPageSelected function in Activity containing the ViewPager. In the next Fragment(which you don't want to load), you write a function let's say showViewContent() where you put in all resource consuming init code and do nothing before onResume() method. Then call showViewContent() function inside onPageSelected. Hope this will help.

2
votes

in my case i wanted to start some animations in views, but with setUserVisibleHint got some issues ...
my solution is :

1/ addOnPageChangeListener for your adapter :

mViewPager.addOnPageChangeListener(this);

2/ implement OnPageChangeListener :

public class PagesFragment extends Fragment implements ViewPager.OnPageChangeListener

3/ override the 3 methodes :

@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
{

}

@Override
public void onPageSelected(int position)
{ 
}

@Override
public void onPageScrollStateChanged(int state)
{
}

4/ declare and initialize this variable on your class

private static int mTabState = 1;

notice : i have three fragments in my adapter, and use mTabState for setCurrentItem and current position of adapter which recognize which fragment is show to user in time ... 5/ in onPageSelected method add this codes :

if (mTabState == 0 || position == 0)
    {
        Intent intent = new Intent("animation");
        intent.putExtra("current_position", position);
        LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent);
    }

if previous page or current page is page 0(fragment in position 0) then do this stuff

6/ now in your fragment class (fragment in position 0 of adapter), you must create broadcast receiver and register it in onResume method and unregister it onPause methos :

BroadcastReceiver broadcastReceiver = new BroadcastReceiver()
{
    @Override
    public void onReceive(Context context, Intent intent)
    {
        if (Objects.equals(intent.getAction(), "animation"))
        {
            int currentPosition = intent.getIntExtra("current_position", 0);
            if (currentPosition == 0)
            {
                startAnimation();
                setViewsVisible();
            } else
            {
                setViewsInvisible();
            }
        }
    }
};

@Override
public void onResume()
{
    super.onResume();
    LocalBroadcastManager.getInstance(mContext).registerReceiver(broadcastReceiver, new IntentFilter("animation"));
}

@Override
public void onPause()
{
    super.onPause();
    LocalBroadcastManager.getInstance(mContext).unregisterReceiver(broadcastReceiver);
}

summary : i have Fragment Pager Adapter witch shows Three Fragments in it, I want show some Animations on Views in Fragment in Position 0 of Adapter, For this I use BroadcastReceiver. When Fragment is Picked I start the Animation method and shows the Views to User, When Fragment is not Showing to User I try to Invisible Views...

1
votes

Please Try This Code for resolve issue of refreshing view in Viewpager....

/* DO NOT FORGET! The ViewPager requires at least “1” minimum OffscreenPageLimit */
int limit = (mAdapter.getCount() > 1 ? mAdapter.getCount() - 1 : 1);
mViewPager.setOffscreenPageLimit(limit);
0
votes

for the "instantiateItem" function, just prepare the fragment, but don't load the heavy content.

Use "onPageChangeListener" , so that each time you go to a specific page, you load its heavy content and show it.

0
votes

I kind of have the same problem. I found some useful code on this site and transform it.

The min int for mViewPager.setOffscreenPageLimit(...); is 1, so even if you change it to 0 you will still have 2 pages loaded.

First thing to do is to create a static int we will call maxPageCount and override FragmentStatePagerAdapter method getCount() to return maxPageCount:

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

Create then a static method accessible from any where in the program that will allow you to change this maxCount:

public static void addPage(){
    maxPageCount++; //or maxPageCount = fragmentPosition+2
    mFragmentStatePagerAdapter.notifyDataSetChanged(); //notifyDataSetChanged is important here.
}

Now initialize maxPageCount to 1. When ever you want you can add another page. In my case when I needed the user to treat the current page first before generated the other. He do it and then, without problem can swipe to the next page.

Hope it help someone.

0
votes

Use This // create boolean for fetching data private boolean isViewShown = false;

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    if (getView() != null) {
        isViewShown = true;
        // fetchdata() contains logic to show data when page is selected mostly asynctask to fill the data
        fetchData();
    } else {
        isViewShown = false;
    }
} 
0
votes

View Pager With only one Page :

This is 2021 - Feburary, Alhamdulillah, I have able to add only one page with viewPager. The approach is with : ViewPager, FragmentPagerAdapter, Tablayout, and a fragment. In my case, I can populate many Pages with many tabs, or only one Page with one Tab. When one tab and one page, on swipe left or right, I can manage to change the chapter of my document (which I want to show next). And when many pages and many tabs, I can change the Entire book of documents.

In main Activity Oncreate : (Here is my working code approach, I have changed nothing here from my working code ):

if(getIntent()!=null){
            if(getIntent().getStringExtra("ONLY_TAFHEEM")!=null)
             sectionsPagerAdapter = new SectionsPagerAdapter(this, getSupportFragmentManager(), suraName, suraId, ayatId, true);
            else
            sectionsPagerAdapter = new SectionsPagerAdapter(this, getSupportFragmentManager(), suraName, suraId, ayatId, false);
        }else {
            sectionsPagerAdapter = new SectionsPagerAdapter(this, getSupportFragmentManager(), suraName, suraId, ayatId, false);
        }

        final ViewPager viewPager = findViewById(R.id.view_pager);
        viewPager.setAdapter(sectionsPagerAdapter);
        tabsLayout = findViewById(R.id.tabs);
        tabsLayout.animate();

        tabsLayout.setupWithViewPager(viewPager);

In Adapter :

 @NonNull
    @Override
    public Fragment getItem(int position) {

          
    // getItem is called to instantiate the fragment for the given page.
    // Return a PlaceholderFragment (defined as a static inner class below).
    return PlaceholderFragment.sendData(mContext, postion, suraName, suraId, ayahId, ARABIC_AYAH, BENGALI_AYAH, actualDbNames[position], tafsirDisplayNames[position]);
}


@Nullable
@Override
public CharSequence getPageTitle(int position) {
   
    return tafsirDisplayNames[position];
}

@Override
public int getCount() {
   // this is the tricky part // Show pages according to array length. // this may only one // this is the tricky part : 
    return tafsirDisplayNames.length;
}

And at last the fragments public constructor :

 public static PlaceholderFragment sendData(Context mContext, int tabIndex, String suraName, String suraId, String ayahNumber, String arabicAyah, String bengaliAyah, String actualDbName, String displayDbName) {

        Log.i("PlaceHolder", "Tafhim sendData: " + bengaliAyah);
        PlaceholderFragment fragment = new PlaceholderFragment();
        Bundle bundle = new Bundle();
        mContext_ = mContext;
        BENGALI_AYAH = bengaliAyah;
        _DISPLAY_DB_NAME = displayDbName;
        bundle.putInt(ARG_SECTION_NUMBER, tabIndex);
        bundle.putString(SURA_NAME, suraName);
        bundle.putString(SURA_ID, suraId);
        bundle.putString(AYAH_NUMBER, ayahNumber);
        bundle.putString(ARABIC_AYAH, arabicAyah);
        bundle.putString(ACTUAL_DB_NAME, actualDbName);
        bundle.putString(DISPLAY_DB_NAME, displayDbName);
        fragment.setArguments(bundle);
        return fragment;
    }

Thats all, just passing the array (of Tab Labels) to the adapter, (it may only one element, in case, for one page), with my need, I can populate one page or more page, and according to this it populate one tab or many tabs : in the above code the array is : tafsirDisplayNames. I can also create the array manually in adapter, when the adapter first called, Or, recreate the array with +-elements, on recreate the MainActivity.

Thats, All, Alhamdulillah, Happy coding....... .

0
votes
// step 1: add  BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT FragmentPagerAdapter contractor
   
    public class BottomTabViewPager extends FragmentPagerAdapter {
    
        private final List<Fragment> mFragmentList = new ArrayList<>();
        private final List<String> mFragmentTitleList = new ArrayList<>();
    
        public BottomTabViewPager(FragmentManager manager) {
            super(manager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
    
        }
    
        @Override
        public Fragment getItem(int position) {
            return mFragmentList.get(position);
        }
    
        @Override
        public int getCount() {
            return mFragmentList.size();
        }
    
        public void addFragment(Fragment fragment, String title) {
            mFragmentList.add(fragment);
            mFragmentTitleList.add(title);
        }
    
        public void addTabs(String title) {
    
            mFragmentTitleList.add(title);
        }
    
    
        @Override
        public CharSequence getPageTitle(int position) {
            return mFragmentTitleList.get(position);
            // return null;
        }
    }
    
    
   // step  2:  You can try like this :

    
    public class MyFragment extends Fragment  {
    
       
    
        public MyFragment() {
            // Required empty public constructor
        }
    
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            // Inflate the layout for this fragment
            View view = inflater.inflate(R.layout.fragment_ui, container, false);
            
            return view;
        }
    
     @Override
        public void onResume() {
            super.onResume();
            /**
             * Load your stuffs here.
             */
        }
    
      }