ViewPager懒加载与Fragment懒加载
什么是懒加载
懒加载是说白了就是延迟加载,对于数据和UI就是在需要的时候再加载展示给用户,什么是需要的时候对于UI来说就是要展示这个UI给用户的时候,在ViewPager与Fragment结合使用的时候就是在Fragment为当前要展示的界面时就是需要的时候
再次也说一下预加载的概念,预加载就是在当前并不需要之后的某个时间需要的加载模式,常用的地方有ART的预加载,ListView的预加载
ViewPager加载当前与相邻item方法为populate(),有mOffscreenPageLimit属性控制前后加载,mOffscreenPageLimit改变可以使用setOffscreenPageLimit(int limit)方法进行设置,看一下方法的源码
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小于DEFAULT_OFFSCREEN_PAGES的话limit会被置换成DEFAULT_OFFSCREEN_PAGES的值,DEFAULT_OFFSCREEN_PAGES的值为1。
通过上面的介绍我们可以知道,简单的通过setOffscreenPageLimit(int limie)方法无法进行ViewPager的懒加载。因此我们需要在Fragment上做文章。
Fragment提供了一个setUserVisibleHint(boolean isVisibleToUser)方法,其中的参数isVisibleToUser就是标识该Fragment对用户是否可见
/**
* Set a hint to the system about whether this fragment's UI is currently visible
* to the user. This hint defaults to true and is persistent across fragment instance
* state save and restore.
* /
public void setUserVisibleHint(boolean isVisibleToUser) {
if (!mUserVisibleHint && isVisibleToUser && mState < STARTED
&& mFragmentManager != null && isAdded()) {
mFragmentManager.performPendingDeferredStart(this);
}
mUserVisibleHint = isVisibleToUser;
mDeferStart = mState < STARTED && !isVisibleToUser;
if (mSavedFragmentState != null) {
// Ensure that if the user visible hint is set before the Fragment has
// restored its state that we don't lose the new value
mSavedUserVisibleHint = mUserVisibleHint;
}
}
通过源码和注释可以知道,该方法实现了FragmentManager的延迟启动Fragment动作和保存状态动作。看注释知道Fragment的UI对用户是否可见的状态该百年时,系统都会回调此方法。
因此我们做延迟加载的话可以在setUserVisibleHint(boolean isVisibleToUser)方法上做文章,但是这个方法有一个坑,在第一次的时候ViewPager会先调用adapter的setUserVisibleHint(boolean isVisibleToUser)方法后调用onCreateView方法,所以做延迟加载需要符合两个条件,onCreateView执行完,setUserVisibleHint方法的参数为true。
因此可以重写两个方法,onViewCreated和setUserVisibleHint。
private var isViewCreated = false
private var isUIVisible = false
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
isViewCreated = true
lazyLoad()
}
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
isUIVisible = isVisibleToUser
if (isUIVisible)
lazyLoad()
}
fun lazyLoad() {
if (isViewCreated && isUIVisible) {
isViewCreated = false
isUIVisible = false
}
}
至此,ViewPager与Fragment结合使用的懒加载就结束了
下面为扩展知识
为什么通过setUserVisibleHint可以知道当前Fragment是否显示,如何实现的。
我们要想知道这个,需要先知道ViewPager是如何加载adapter中的数据的。我们知道ViewPager是使用的经典的适配器模式进行编写的,它的配套适配器是PagerAdapter,这是一个抽象类,直接继承自Object。该类中有一个setPrimaryItem方法,此方法在ViewPager的populate方法中会调用,populate相信很多人都会,是对于页面的创建与销毁,如果当前的iteminfo不为null则会触发Adapter.setPrimaryItem方法,此方法会传入当前iteminfo的object,对于PagerAdapter就是instantiateItem方法返回的Object,但是在PagerAdapter中setPrimaryItem并没有实现逻辑,但是在FragmentPagerAdapter和FragmentStatePagerAdapter中实现了setPrimaryItem的逻辑,如下
@Override
@SuppressWarnings("ReferenceEquality")
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;
}
}
将传进来的object强转成Fragment,判断与当前的mCurrentPrimaryItem是否是一个,如果不是调用mCurrentPrimaryItem的setMenuVisibility和setUserVisibleHint方法,传入false,告知mCurrentPrimaryItem移除界面,并调用传入的Fragment的上述两个方法,告知其显示在界面中,最后将传入的fragment设置给mCurrentPrimaryItem进行保存。