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字段,实现实时的刷新。

posted @ 2017-08-16 10:52  一名Android小生  阅读(1133)  评论(0编辑  收藏  举报