普通的viewpager如果你不使用setoffscreenpagelimit(int limit)这个方法去设置默认加载数的话是会默认加载页面的左右两页的,也就是说当你进入viewpager第一页的时候第二页和第一页是会被一起加载的,这样同时加载就会造成一些问题,试想我们如果设置了setoffscreenpagelimit为3的话,那么进入viewpager以后就会同时加载4个fragment,像我们平时的项目中在这些fragment中一般都是会发送网络请求的,也就是说我们有4个fragment同时发送网络请求去获取数据,这样的结果显而易见给用户的体验是不好的(如:浪费用户流量,造成卡顿等等)。
懒加载的实现弊端
ViewPager的预加载机制。那么,我们可不可以设置ViewPager的预加载为0,不就解决问题了吗?也就是代码这样操作:
`vp.setOffscreenPageLimit(0);`
然后看一下源码
`public void setOffscreenPageLimit(int limit) {
if (limit < 1) {
Log.w("ViewPager", "Requested offscreen page limit " + limit + " too small; defaulting to " + 1);
limit = 1;
}
if (limit != this.mOffscreenPageLimit) {
this.mOffscreenPageLimit = limit;
this.populate();
}
}`
ViewPager默认情况下的加载,当切换到当前页面时,会默认预加载左右两侧的布局到ViewPager中,尽管两侧的View并不可见的,我们称这种情况叫预加载;由于ViewPager对offscreenPageLimit设置了限制,页面的预加载是不可避免……
初始化缓存(mOffscreenPageLimit == 1)
中间页面缓存(mOffscreenPageLimit == 1)
ViewPager.setAdapter方法
ViewPager.populate(int newCurrentItem)
ViewPager.dataSetChanged()
ViewPager.scrollToItem(int item, boolean smoothScroll, int velocity, boolean dispatchSelected)
ViewPager.calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCurInfo)
发现Fragment中有一个setUserVisibleHint(boolean isVisibleToUser)方法,这个方法就是告诉用户,UI对用户是否可见,可以做懒加载初始化操作。
懒加载需要处理的几个问题
视图保存
是否已经被用户所看到
主要的方法是Fragment中的setUserVisibleHint(),此方法会在onCreateView()之前执行,当viewPager中fragment改变可见状态时也会调用,当fragment 从可见到不见,或者从不可见切换到可见,都会调用此方法,使用getUserVisibleHint() 可以返回fragment是否可见状态。在BaseLazyFragment中需要在onActivityCreated()及setUserVisibleHint()方法中都调了一次lazyLoad() 方法。如果仅仅在setUserVisibleHint()调用lazyLoad(),当默认首页首先加载时会导致viewPager的首页第一次展示时没有数据显示,切换一下才会有数据。因为首页fragment的setUserVisible()在onActivityCreated() 之前调用,此时isPrepared为false 导致首页fragment 没能调用onLazyLoad()方法加载数据。
`/**
*
* @author yangchong
* blog : https://github.com/yangchong211
* time : 2017/7/22
* desc : 懒加载
* revise: 懒加载时机:onCreateView()方法执行完毕 + setUserVisibleHint()方法返回true
*
*/
public abstract class BaseLazyFragment extends BaseFragment {
/*
* 预加载页面回调的生命周期流程:
* setUserVisibleHint() -->onAttach() --> onCreate()-->onCreateView()-->
* onActivityCreate() --> onStart() --> onResume()
*/
/**
* 懒加载过
*/
protected boolean isLazyLoaded = false;
/**
* Fragment的View加载完毕的标记
*/
private boolean isPrepared = false;
/**
* 第一步,改变isPrepared标记
* 当onViewCreated()方法执行时,表明View已经加载完毕,此时改变isPrepared标记为true,并调用lazyLoad()方法
*/
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
isPrepared = true;
//只有Fragment onCreateView好了
//另外这里调用一次lazyLoad()
lazyLoad();
}
/**
* 第二步
* 此方法会在onCreateView()之前执行
* 当viewPager中fragment改变可见状态时也会调用
* 当fragment 从可见到不见,或者从不可见切换到可见,都会调用此方法
* true表示当前页面可见,false表示不可见
*/
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
LogUtil.d("setUserVisibleHint---"+isVisibleToUser);
//只有当fragment可见时,才进行加载数据
if (isVisibleToUser){
lazyLoad();
}
}
/**
* 调用懒加载
* 第三步:在lazyLoad()方法中进行双重标记判断,通过后即可进行数据加载
*/
private void lazyLoad() {
if (getUserVisibleHint() && isPrepared && !isLazyLoaded) {
showFirstLoading();
onLazyLoad();
isLazyLoaded = true;
} else {
//当视图已经对用户不可见并且加载过数据,如果需要在切换到其他页面时停止加载数据,可以覆写此方法
if (isLazyLoaded) {
stopLoad();
}
}
}
/**
* 视图销毁的时候讲Fragment是否初始化的状态变为false
*/
@Override
public void onDestroyView() {
super.onDestroyView();
isLazyLoaded = false;
isPrepared = false;
}
/**
* 第一次可见时,操作该方法,可以用于showLoading操作,注意这个是全局加载loading
*/
protected void showFirstLoading() {
LogUtil.i("第一次可见时show全局loading");
}
/**
* 停止加载
* 当视图已经对用户不可见并且加载过数据,但是没有加载完,而只是加载loading。
* 如果需要在切换到其他页面时停止加载数据,可以覆写此方法。
* 存在问题,如何停止加载网络
*/
protected void stopLoad(){
}
/**
* 第四步:定义抽象方法onLazyLoad(),具体加载数据的工作,交给子类去完成
*/
@UiThread
protected abstract void onLazyLoad();
}`
onLazyLoad()加载数据条件
还有几个细节需要优化一下
什么是状态管理器?
如何降低偶性和入侵性
让View状态的切换和Activity彻底分离开,必须把这些状态View都封装到一个管理类中,然后暴露出几个方法来实现View之间的切换。在不同的项目中可以需要的View也不一样,所以考虑把管理类设计成builder模式来自由的添加需要的状态View。
那么如何降低耦合性,让代码入侵性低。方便维护和修改,且移植性强呢?大概具备这样的条件……
那么具体怎么操作呢?
可以自由切换内容,空数据,异常错误,加载,网络错误等5种状态。父类BaseFragment直接暴露5中状态,方便子类统一管理状态切换,这里fragment的封装和activity差不多。
* @author yangchong
* blog : https://github.com/yangchong211
* time : 2017/7/20
* desc : fragment的父类
* revise: 注意,该类具有懒加载
*
*/
public abstract class BaseStateFragment extends BaseLazyFragment {
protected StateLayoutManager statusLayoutManager;
private View view;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
if(view==null){
view = inflater.inflate(R.layout.base_state_view, container , false);
initStatusLayout();
initBaseView(view);
}
return view;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initView(view);
initListener();
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
/**
* 获取到子布局
* @param view view
*/
private void initBaseView(View view) {
LinearLayout llStateView = view.findViewById(R.id.ll_state_view);
llStateView.addView(statusLayoutManager.getRootLayout());
}
/**
* 初始化状态管理器相关操作
*/
protected abstract void initStatusLayout();
/**
* 初始化View的代码写在这个方法中
* @param view view
*/
public abstract void initView(View view);
/**
* 初始化监听器的代码写在这个方法中
*/
public abstract void initListener();
/**
* 第一次可见状态时,showLoading操作,注意下拉刷新操作时不要用该全局loading
*/
@Override
protected void showFirstLoading() {
super.showFirstLoading();
showLoading();
}
/*protected void initStatusLayout() {
statusLayoutManager = StateLayoutManager.newBuilder(activity)
.contentView(R.layout.common_fragment_list)
.emptyDataView(R.layout.view_custom_empty_data)
.errorView(R.layout.view_custom_data_error)
.loadingView(R.layout.view_custom_loading_data)
.netWorkErrorView(R.layout.view_custom_network_error)
.build();
}*/
/*---------------------------------下面是状态切换方法-----------------------------------------*/
/**
* 加载成功
*/
protected void showContent() {
if (statusLayoutManager!=null){
statusLayoutManager.showContent();
}
}
/**
* 加载无数据
*/
protected void showEmptyData() {
if (statusLayoutManager!=null){
statusLayoutManager.showEmptyData();
}
}
/**
* 加载异常
*/
protected void showError() {
if (statusLayoutManager!=null){
statusLayoutManager.showError();
}
}
/**
* 加载网络异常
*/
protected void showNetWorkError() {
if (statusLayoutManager!=null){
statusLayoutManager.showNetWorkError();
}
}
/**
* 加载loading
*/
protected void showLoading() {
if (statusLayoutManager!=null){
statusLayoutManager.showLoading();
}
}
}
//如何切换状态呢?
showContent();
showEmptyData();
showError();
showLoading();
showNetWorkError();
//或者这样操作也可以
statusLayoutManager.showLoading();
statusLayoutManager.showContent();`
状态管理器的设计思路
StateFrameLayout是继承FrameLayout自定义布局,主要是存放不同的视图,以及隐藏和展示视图操作
StateLayoutManager是状态管理器,主要是让开发者设置不同状态视图的view,以及切换视图状态操作:几种异常状态要用ViewStub,因为在界面状态切换中loading和内容View都是一直需要加载显示的,但是其他的3个只有在没数据或者网络异常的情况下才会加载显示,所以用ViewStub来加载他们可以提高性能。
OnRetryListener,为接口,主要是重试作用。比如加载失败了,点击视图需要重新刷新接口,则可以用到这个。开发者也可以自己设置点击事件
关于状态视图切换方案,目前市场有多种做法,具体可以看我的这篇博客:
项目地址:https://github.com/yangchong211/YCStateLayout