日常开发中很多场景会用到ViewPager+Fragment,一般情况下,我们是通过setOffscreenPageLimit(int limit)方法控制页面的预加载数量。但是有时候,我们又会有这样的需求,当用户滑动到相关页面的时候,才加载相关页面的数据,从而节省用户流量。那么,设置setOffscreenPageLimit(int limit)是否可以达到这样的目的呢,答案是肯定的:不能!
为什么不能呢,先上源码:
private static final int DEFAULT_OFFSCREEN_PAGES = 1;
/**
* 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.
*
* <p>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.</p>
*
* <p>You should keep this limit low, especially if your pages have complex layouts.
* This setting defaults to 1.</p>
*
* @param limit How many pages will be kept offscreen in an idle state.
*/
public void setOffscreenPageLimit(int limit) {
if (limit < DEFAULT_OFFSCREEN_PAGES) {
Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to "
+ DEFAULT_OFFSCREEN_PAGES);
limit = DEFAULT_OFFSCREEN_PAGES;
}
if (limit != mOffscreenPageLimit) {
mOffscreenPageLimit = limit;
populate();
}
}
可以看到如果设置的limit小于默认的offscreen_pages,就采用默认值,而默认值是1,也就是说ViewPager会加载当前页面的前后两个页面!
网上也有不少关于如何达到懒加载的文章,比如:实现ViewPager懒加载的三种方法,这篇博文介绍了三种方法。同时第三种方法是博主推荐的,而这种方法也是我今天想要介绍的。第三种方法实现懒加载demo的github地址为:懒加载Demo。
这个Demo基本能够实现懒加载,但是我在使用过程中,我发现相关的Fragment的setUserVisibleHint(boolean isVisibleToUser)居然失效了!。
我们知道当ViewPager+Fragment,当adapter继承FragmentPagerAdater后,要知道相应的Frgement可见还是不可见,不能通过onStop()和onResume()来实现,需通过setUserVisibleHint(boolean isVisibleToUser)
来判断,那么证明,setUserVisibleHint(boolean isVisibleToUser)应当在必要的地方中进行调用了。
先看一下LazyFragmentPagerAdapter中关于setUserVisibleHint(boolean isVisibleToUser)的代码:
public abstract class LazyFragmentPagerAdapter extends LazyPagerAdapter<Fragment> {
......
@Override
public Object instantiateItem(ViewGroup container, int position) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
final long itemId = getItemId(position);
// Do we already have this fragment?
String name = makeFragmentName(container.getId(), itemId);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
mCurTransaction.attach(fragment);
} else {
fragment = getItem(container, position);
if (fragment instanceof Laziable) {
mLazyItems.put(position, fragment);
} else {
mCurTransaction.add(container.getId(), fragment, name);
}
}
if (fragment != getCurrentItem()) {
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
}
return fragment;
}
......
}
在LazyFragmentPagerAdapter 全文上下中,只用到一个地方的setUserVisibleHint(boolean isVisibleToUser),而且是设置为false了。
我们再来看看FragmentPagerAdapter中是如何调用setUserVisibleHint(boolean isVisibleToUser)的吧:
public abstract class FragmentPagerAdapter extends PagerAdapter {
......
@Override
public Object instantiateItem(ViewGroup container, int position) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
final long itemId = getItemId(position);
// Do we already have this fragment?
String name = makeFragmentName(container.getId(), itemId);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
mCurTransaction.attach(fragment);
} else {
fragment = getItem(position);
if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), itemId));
}
if (fragment != mCurrentPrimaryItem) {
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
}
return fragment;
}
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment)object;
if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) {
mCurrentPrimaryItem.setMenuVisibility(false);
mCurrentPrimaryItem.setUserVisibleHint(false);
}
if (fragment != null) {
fragment.setMenuVisibility(true);
fragment.setUserVisibleHint(true);
}
mCurrentPrimaryItem = fragment;
}
}
......
}
在FragmentPagerAdapter中的setPrimaryItem()方法中,可知如果当前的mCurrentPrimaryItem不是将要展示的Fragment,就将mCurrentPrimaryItem通过调用setUserVisibleHint告诉我们该Fragment将不可见,同时将要展示的Fragment的设置为可见,并将将要展示的Fragment赋值给mCurrentPrimaryItem。
对比,LazyFragmentPagerAdapter缺少如此部分设置,故增加如下:
/**
* 通过viewpager中的代码进行调用:mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);
* 重写此方法,仿照FragmentPagerAdapter设置当前fragment visible or invisible
* @param container
* @param position
* @param object
*/
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
super.setPrimaryItem(container, position, object);
Fragment fragment = (Fragment)object;
if (fragment != getCurrentItem()) {
if (getCurrentItem() != null) {
getCurrentItem().setMenuVisibility(false);
getCurrentItem().setUserVisibleHint(false);
}
if (fragment != null) {
fragment.setMenuVisibility(true);
fragment.setUserVisibleHint(true);
}
setCurrentItem(fragment);
}
}
好了,通过增加以上方法,即能够实现懒加载且可通过setUserVisibleHint()判断Fragment是否可见了。
使用该懒加载方法应注意以下几点:
1、所有的Fragmeng实现LazyFragmentPagerAdapter.Laziable接口。
2、自定义Adapter继承LazyFragmentPagerAdapter。
3、ViewPager替换为LazyViewPager。
这是我目前接触到的对原有的ViewPager+Fragment改造比较方便的方法,给大神如果有更好的方法,望请推荐!