ViewPager懒加载极致优化(1),详细的Android学习指南

本文详细探讨了Android中ViewPager的懒加载和预加载机制,包括懒加载出现问题的原因和解决办法,如何实现预加载,以及如何配合状态管理器优化用户体验。通过setUserVisibleHint()方法和自定义BaseLazyFragment实现懒加载,确保数据在Fragment可见时才加载。同时,文章还讨论了状态管理器在加载中、加载失败和空数据状态的统一管理,降低耦合性和侵入性。
摘要由CSDN通过智能技术生成
  • 将不需要的ItemInfo移除: mItems.remove(itemIndex),并调用mAdapter.destroyItem方法

  • 设置LayoutParams参数(包括position和widthFactor),根据position排序待绘制的View列表:mDrawingOrderedChildren,重写了getChildDrawingOrder方法

  • 最后一步获取当前显示View的焦点:currView.requestFocus(View.FOCUS_FORWARD)

ViewPager.dataSetChanged()

  • 当调用Adapter的notifyDataSetChanged时,会触发这个方法,该方法会重新计算当前页面的position,

  • 移除需要销毁的页面的ItemInfo对象,然后再调用populate方法刷新页面

  • 循环mItems(每个page对应的ItemInfo对象),调用int newPos = mAdapter.getItemPosition方法

  • 当newPos等于PagerAdapter.POSITION_UNCHANGED表示当前页面不需要更新,不用销毁,当newPos等于PagerAdapter.POSITION_NONE时,需要更新,移除item,调用mAdapter.destroyItem

  • 循环完成后,最后计算出显示页面的newCurrItem,调用setCurrentItemInternal(newCurrItem, false, true)方法更新UI(实际调用populate方法重新计算页面信息)

ViewPager.scrollToItem(int item, boolean smoothScroll, int velocity, boolean dispatchSelected)

  • 滑动到指定页面,内部会触发OnPageChangeListener

ViewPager.calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCurInfo)

  • 这个方法主要用于计算每个页面对应ItemInfo的offset变量,这个变量用于记录当前view在所有缓存View中(包含当前显示页)的索引,用于布局的时候计算该View应该放在哪个位置

  • 在populate方法中更新完页面数据后,会调用该方法计算所有页面的offset

05.懒加载出现问题

发现Fragment中有一个setUserVisibleHint(boolean isVisibleToUser)方法,这个方法就是告诉用户,UI对用户是否可见,可以做懒加载初始化操作。

  • 因为ViewPager会加载好多Fragment,为了节省内容等会在Fragment不可见的某个时候调用onDestroyView()将用户界面销毁掉但是Fragment的实例还在,所以可能第一次加载没有问题,但是再次回到第一个Fragment再去加载的时候就会出现UI对用户可见但是视图还没有初始化。

懒加载需要处理的几个问题

  • 预加载,虽然没有显示在界面上,但是当前页面的上一页和下一页的Fragment已经执行了一个Fragment能够显示在界面上的所有生命周期方法,但是我们想在跳转到该页时才真正构造数据视图和请求数据。那么我们可以使用一个占位视图,那么可以想到使用ViewStub,当真正跳转到该页时,执行ViewStub.inflate()方法,加载真正的数据视图和请求数据。

视图保存

  • 当某一页超出可视范围和预加载范围,那么它将会被销毁,FragmentStatePagerAdapter销毁整个Fragment, 我们可以自己保存该Fragment,或使用FragmentPagerAdapter让FragmentTransition来保留Fragment的引用。虽然这样,但是它的周期方法已经走完,那么我们只能手动的保存Fragment根View的引用,当再次重新进入新的声明周期方法时,返回原来的View

是否已经被用户所看到

  • 其实本身而言,FragmentManager并没有提供为Fragment被用户所看到的回调方法,而是在FragmentPagerAdapter和FragmentStatePagerAdapter中,调用了Fragment.setUserVisibleHint(boolean)来表明Fragment是否已经被作为primaryFragment. 所以这个方法可以被认为是一个回调方法。
06.如何实现预加载机制

主要的方法是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() --> on
Start() --> 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()加载数据条件

  • getUserVisibleHint()会返回是否可见状态,这是fragment实现懒加载的关键,只有fragment 可见才会调用onLazyLoad() 加载数据。

  • isPrepared参数在系统调用onActivityCreated时设置为true,这时onCreateView方法已调用完毕(一般我们在这方法里执行findviewbyid等方法),确保 onLazyLoad()方法不会报空指针异常。

  • isLazyLoaded确保ViewPager来回切换时BaseFragment的initData方法不会被重复调用,onLazyLoad在该Fragment的整个生命周期只调用一次,第一次调用onLazyLoad()方法后马上执行 isLazyLoaded = true。

  • 然后再继承这个BaseLazyFragment实现onLazyLoad() 方法就行。他会自动控制当fragment 展现出来时,才会加载数据

还有几个细节需要优化一下

  • 当视图已经对用户不可见并且加载过数据,如果需要在切换到其他页面时停止加载数据,可以覆写此方法,也就是stopLoad

  • 视图销毁的时候讲Fragment是否初始化的状态变为false,这个也需要处理一下

  • 第一次可见时,定义一个showFirstLoading方法,操作该方法,可以用于Loading加载操作,注意这个是全局加载loading,和下拉刷新数据或者局部刷新的loading不一样的。可能有些开发app,没有将loading分的这么细。

07.懒加载配合状态管理器

什么是状态管理器?

  • 一般在需要用户等待的场景,显示一个Loading动画可以让用户知道App正在加载数据,而不是程序卡死,从而给用户较好的使用体验。

  • 当加载的数据为空时显示一个数据为空的视图、在数据加载失败时显示加载失败对应的UI并支持点击重试会比白屏的用户体验更好一些。

  • 加载中、加载失败、空数据的UI风格,一般来说在App内的所有页面中需要保持一致,也就是需要做到全局统一。

如何降低偶性和入侵性

让View状态的切换和Activity彻底分离开,必须把这些状态View都封装到一个管理类中,然后暴露出几个方法来实现View之间的切换。在不同的项目中可以需要的View也不一样,所以考虑把管理类设计成builder模式来自由的添加需要的状态View。

那么如何降低耦合性,让代码入侵性低。方便维护和修改,且移植性强呢?大概具备这样的条件……

  • 可以运用在activity或者fragment中

  • 不需要在布局中添加LoadingView,而是统一管理不同状态视图,同时暴露对外设置自定义状态视图方法,方便UI特定页面定制

  • 支持设置自定义不同状态视图,即使在BaseActivity统一处理状态视图管理,也支持单个页面定制

  • 在加载视图的时候像异常和空页面能否用ViewStub代替,这样减少绘制,只有等到出现异常和空页面时,才将视图给inflate出来

  • 当页面出现网络异常页面,空页面等,页面会有交互事件,这时候可以设置点击设置网络或者点击重新加载等等

那么具体怎么操作呢?

可以自由切换内容,空数据,异常错误,加载,网络错误等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
 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值