ViewPager和Fragment实现页面的懒加载
ViewPager+Fragment 懒加载问题
我们在做应用开发的时候,一个Activity里面可能会以viewpager(或其他容器)与多个Fragment来组合使用,而如果每个fragment都需要去加载数据,或从本地加载,或从网络加载,那么在这个activity刚创建的时候就变成需要初始化大量资源。这样的结果,我们当然不会满意。那么,能不能做到当切换到这个fragment的时候,它才去初始化呢?
答案就在Fragment里的setUserVisibleHint这个方法里。
public class AFragment extends Fragment { @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); }
由上面的方法可以看出 isVisibleToUser字段决定了Fragement是否对用户可见,那么要实现Fragment的懒加载,我们必定要在这个方法中做文章,首先我们先看一下通过ViewPager管理Fragement的时候,Fragement生命周期回调。首先布局很简单,就是4个Fragement的切换,通过FragmentPagerAdapter实现数据的填充,这里还有一个FragementStatePagerAdapter,稍后再说
首先初始化了四个Fragment,分别是AFragment,BFragment,CFragment,DFragment。ViewPager默认显示第一条数据,日志如下,
我们可以看到,setUserVisibleHint方法首先被回调。紧接着实现AFragment的加载,然后又实现了BFragment的加载,这里我们可以看出来ViewPager会预先加载下一个Fragement。
08-14 21:22:25.652 9467-9467/? D/AFragment: setUserVisibleHint: false 08-14 21:22:25.652 9467-9467/? D/BFragment: setUserVisibleHint: false 08-14 21:22:25.652 9467-9467/? D/AFragment: setUserVisibleHint: true 08-14 21:22:25.653 9467-9467/? D/AFragment: onAttach: 08-14 21:22:25.653 9467-9467/? D/AFragment: onCreate: 08-14 21:22:25.653 9467-9467/? D/BFragment: onAttach: 08-14 21:22:25.653 9467-9467/? D/BFragment: onCreate: 08-14 21:22:25.653 9467-9467/? D/AFragment: onCreateView: 08-14 21:22:25.655 9467-9467/? D/AFragment: onActivityCreated: 08-14 21:22:25.655 9467-9467/? D/AFragment: onStart: 08-14 21:22:25.655 9467-9467/? D/AFragment: onResume: 08-14 21:22:25.655 9467-9467/? D/BFragment: onCreateView: 08-14 21:22:25.656 9467-9467/? D/BFragment: onActivityCreated: 08-14 21:22:25.656 9467-9467/? D/BFragment: onStart: 08-14 21:22:25.656 9467-9467/? D/BFragment: onResume:
当我们滑动到BFragement的时候,日志如下
08-14 21:27:35.715 9467-9467/com.zhiji.viewpagerandfragment D/CFragment: setUserVisibleHint: false 08-14 21:27:35.715 9467-9467/com.zhiji.viewpagerandfragment D/AFragment: setUserVisibleHint: false 08-14 21:27:35.715 9467-9467/com.zhiji.viewpagerandfragment D/BFragment: setUserVisibleHint: true 08-14 21:27:35.715 9467-9467/com.zhiji.viewpagerandfragment D/CFragment: onAttach: 08-14 21:27:35.715 9467-9467/com.zhiji.viewpagerandfragment D/CFragment: onCreate: 08-14 21:27:35.716 9467-9467/com.zhiji.viewpagerandfragment D/CFragment: onCreateView: 08-14 21:27:35.721 9467-9467/com.zhiji.viewpagerandfragment D/CFragment: onActivityCreated: 08-14 21:27:35.722 9467-9467/com.zhiji.viewpagerandfragment D/CFragment: onStart: 08-14 21:27:35.722 9467-9467/com.zhiji.viewpagerandfragment D/CFragment: onResume:
可以看出 首先setUserVisibleHint方法设置对用户的状态。然后预加载了CFragment.这时候界面上已经有了三个Fragement存在分别是AFragement,BFragment,CFragment.说明ViewPager默认最多会缓存左右各一个Fragement,当然我们也可以修改这一默认值,通过mViewPager.setOffscreenPageLimit(int pageCount);这里注意:pageCount的值代表,ViewPager允许左边或者右边最大保存的页面数量,比如设置为2,代表左边最多为两个,右边最多为两个,那么这时候页面上最多缓存5个Fragment
接下来我们继续滑动到CFragement,日志如下:
这里可以看出不同的地方在于,AFragement执行了 onPause ----> onStop---->onDestroyView方法。这是因为ViewPager默认最大缓存左右两边各一个,所以导致了AFragement的视图销毁。
08-14 21:35:23.598 20813-20813/com.zhiji.viewpagerandfragment D/DFragment: setUserVisibleHint: false 08-14 21:35:23.598 20813-20813/com.zhiji.viewpagerandfragment D/BFragment: setUserVisibleHint: false 08-14 21:35:23.598 20813-20813/com.zhiji.viewpagerandfragment D/CFragment: setUserVisibleHint: true 08-14 21:35:23.599 20813-20813/com.zhiji.viewpagerandfragment D/DFragment: onAttach: 08-14 21:35:23.599 20813-20813/com.zhiji.viewpagerandfragment D/DFragment: onCreate: 08-14 21:35:23.599 20813-20813/com.zhiji.viewpagerandfragment D/AFragment: onPause: 08-14 21:35:23.599 20813-20813/com.zhiji.viewpagerandfragment D/AFragment: onStop: 08-14 21:35:23.599 20813-20813/com.zhiji.viewpagerandfragment D/AFragment: onDestroyView: 08-14 21:35:23.600 20813-20813/com.zhiji.viewpagerandfragment D/DFragment: onCreateView: 08-14 21:35:23.603 20813-20813/com.zhiji.viewpagerandfragment D/DFragment: onActivityCreated: 08-14 21:35:23.603 20813-20813/com.zhiji.viewpagerandfragment D/DFragment: onStart: 08-14 21:35:23.603 20813-20813/com.zhiji.viewpagerandfragment D/DFragment: onResume:
紧接着我们滑动到DFragment 日志如下
原理同上,CFragment会走onPause ----> onStop---->onDestroyView方法。
08-14 21:37:39.813 20813-20813/com.zhiji.viewpagerandfragment D/CFragment: setUserVisibleHint: false 08-14 21:37:39.814 20813-20813/com.zhiji.viewpagerandfragment D/DFragment: setUserVisibleHint: true 08-14 21:37:39.814 20813-20813/com.zhiji.viewpagerandfragment D/BFragment: onPause: 08-14 21:37:39.814 20813-20813/com.zhiji.viewpagerandfragment D/BFragment: onStop: 08-14 21:37:39.814 20813-20813/com.zhiji.viewpagerandfragment D/BFragment: onDestroyView:
上面有提到FragmentStatePagerAdapter,这里说下他和FragmentPagerAdapter的区别,使用FragmentPagerAdapter,ViewPager会缓存Fragment的所有实例,只会调用其onDestroyView方法,而对于FragmentStatePagerAdapter,当视图销毁时,会回调它的onDestroy(),onDetach()方法,将其从ViewPager中移除,具体使用情况根据实际需求来定。
还有一个小问题,针对Fragment的onCreateView()方法,。我们可以进行视图的缓存,刚才说到了使用FragmentPagerAdapter的时候,不过要注意,使用缓存的视图,要先把ViewPager中的旧视图移除,。避免重复添加,代码参考如下:
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { Log.d(TAG, "onCreateView: "); //view视图的缓存 if (mView == null) { mView = inflater.inflate(R.layout.fragment_a, container, false); } ViewGroup parent = (ViewGroup) mView.getParent(); if (parent != null) { parent.removeView(mView); } return mView; }
接下来进入我们的正题,针对ViewPager的预加载机制我们如何禁止呢。尝试通过mViewPager.setOffscreenPageLimit(0);通过源码得知,当数量小于1时,将值赋值为1,所以不可行。那么可以修改其源码,这个哟点太重量级了,下面介绍一种常用的思路。
找到刚才的setUserVisibleHint方法
public class LazyFragment extends Fragment { private static final String TAG = "LazyFragment"; protected boolean isViewCreated;//View视图创建完成 protected boolean isDataLoaded;//标记数据已经加载了,避免页面之间的切换,重复加载数据 private View mView;
public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); Log.d(this.getClass().getSimpleName(), "setUserVisibleHint: " + isVisibleToUser); prepareData(true); }
/** * 准备数据 isForceUpdate是否强制刷新数据, */ private void prepareData(boolean isForceUpdate) { //判断条件,可见,View视图已经创建完成,数据没有被加载 是否强制刷新数据 if (getUserVisibleHint() && isViewCreated && (!this.isDataLoaded || isForceUpdate)) { Log.d(this.getClass().getSimpleName(), "prepareData: " + "加载数据了。。。"); isDataLoaded = true; } } public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); Log.d(TAG, "onActivityCreated: "); isViewCreated = true; prepareData(true); }
以上便是实现方式,下面来具体分析,
首先在这个方法中setUserVisibleHint调用拉取数据。这里判断是否对于用户可见,还要判断视图是否加载完毕,因为此方法的调用在onCreateView之前。所以避免空指针,加上此判断。还有isDataLoaded字段
此字段是为了避免数据的重复加载,但是字段一旦赋值后,在整个滑动的过程中,因为Activity中的Fragement集合持有Fragement实例,所以该对象不会被回收,所以该成员变量一直是true
那么我们如果对于某个特定的Fragement想即时刷新怎么办,这里可以新加一个isForceUpdate字段,实现实时的刷新。