印象中从 Feed 流应用流行开始,Fragment
懒加载变成了一个你们都须要关注的开发知识,关于 Fragment
的懒加载,网上有不少例子,GitHub 上也有不少例子,就连我本身在一年前也写过相关的文章。可是以前的应用可能最多的是一层 Activity + ViewPager
的 UI 层次,可是随着页面愈来愈复杂,愈来愈多的应用首页一个页面外层是一个 ViewPager
内部可能还嵌套着一层 ViewPager
,这是以前的懒加载就可能不那么好用了。本文对于多层 ViewPager
的嵌套使用过程当中,Fragment
主要的三个状态:第一次可见,每次可见,每次不可见,提供解决方案。java
在咱们开发中常常会使用 ViewPager + Fragment
来建立多 tab 的页面,此时在 ViewPager
内部默认会帮咱们缓存当页面先后两个页面的 Fragment
内容,若是使用了 setOffscreenPageLimit
方法,那么 ViewPager
初始化的时候将会缓存对应参数个 Fragment。为了增长用户体验咱们每每会使用该方法来保证加载过的页面不被销毁,并留离开 tab 以前的状态(列表滑动距离等),而咱们在使用 Fragment
的时候每每在建立完 View
后,就会开始网络请求等操做,若是存在上述的需求时,懒加载就显得尤其重要了,不只能够节省用户流量,还能够在提升应用性能,给用户带来更加的体验。android
ViewPager + Fragment
的懒加载实质上咱们就在作三件事,就能够将上边所说的效果实现,那么就是找到每一个 Fragment
第一对用户可见的时机,和每次 Fragment 对用户可见时机,以及每次 Framgment
对用户不可见的时机,来暴露给实现实现类作对应的网络请求或者网络请求中断时机。下面咱们就来从常见的几种 UI 结构上一步步实现不管嵌套多少层,不管开发者使用的 hide show
仍是 ViewPager
嵌套都能准确获取这三种状态的时机的一种懒加载实现方案。git
咱们都知道 Fragment 生命周期按前后顺序有github
onAttach -> onCreate -> onCreatedView -> onActivityCreated -> onStart -> onResume -> onPause -> onStop -> onDestroyView -> onDestroy -> onDetach缓存
对于 ViewPager + Fragment 的实现咱们须要关注的几个生命周期有:bash
onCreatedView + onActivityCreated + onResume + onPause + onDestroyView微信
以及非生命周期函数:网络
setUserVisibleHint + onHiddenChangedapp
对于单层 ViewPager + Fragment 多是咱们最经常使用的页面结构了,如网易云音乐的首页顶部的是三个 tab ,咱们那网易云音乐做为例子:ide
对于这种 ViewPager + Fragment 结构,咱们使用的过程当中通常只包含是 3 种状况分别是:
使用 FragmentPagerAdapter
,FragmentPagerStateAdapter
不设置 setOffscreenPageLimit
使用 FragmentPagerAdapter
,FragmentPagerStateAdapter
设置 setOffscreenPageLimit
为 tab 总数
进入其余页面或者用户按 home 键回到桌面,当前 ViewPager 页面变成不见状态。
对于 FragmentPagerAdapter
和 FragmentPagerStateAdapter
的区别在于在于,前者在 Fragment
不见的时候将不会 detach
,然后者将会销毁 Fragment
并 detach
掉。
实际上这也是全部 ViewPager
的操做状况。
setOffscreenPageLimit
左右滑动页面/或者每次选择相邻 tab 的状况 FragmentPagerAdapter
和 FragmentPagerStateAdapter
有所区别BottomTabFragment1 setUserVisibleHint false
BottomTabFragment2 setUserVisibleHint false
BottomTabFragment1 setUserVisibleHint true
BottomTabFragment1 onCreateView
BottomTabFragment1 onActivityCreated
BottomTabFragment1 onResume
BottomTabFragment2 onCreateView
BottomTabFragment2 onActivityCreated
BottomTabFragment2 onResume
//滑动到 Tab 2
BottomTabFragment3 setUserVisibleHint false
BottomTabFragment1 setUserVisibleHint false
BottomTabFragment2 setUserVisibleHint true
BottomTabFragment3 onCreateView
BottomTabFragment3 onActivityCreated
BottomTabFragment3 onResume
//跳过 Tab3 直接选择 Tab4
BottomTabFragment4 setUserVisibleHint false
BottomTabFragment2 setUserVisibleHint false
BottomTabFragment4 setUserVisibleHint true
BottomTabFragment4 onCreateView
BottomTabFragment4 onActivityCreated
BottomTabFragment4 onResume
BottomTabFragment2 onPause
BottomTabFragment2 onDestroyView
// FragmentPagerStateAdapter 会走一下两个生命周期方法
BottomTabFragment2 onDestroy
BottomTabFragment2 onDetach
BottomTabFragment1 onPause
BottomTabFragment1 onDestroyView
// FragmentPagerStateAdapter 会走一下两个生命周期方法
BottomTabFragment1 onDestroy
BottomTabFragment1 onDetach
// 用户回到桌面 再回到当前 APP 打开其余页面当前页面的生命周期也是这样的
BottomTabFragment3 onPause
BottomTabFragment4 onPause
BottomTabFragment3 onStop
BottomTabFragment4 onStop
BottomTabFragment3 onResume
BottomTabFragment4 onResume
复制代码
BottomTabFragment1 setUserVisibleHint false
BottomTabFragment2 setUserVisibleHint false
BottomTabFragment3 setUserVisibleHint false
BottomTabFragment4 setUserVisibleHint false
BottomTabFragment1 setUserVisibleHint true
BottomTabFragment1 onCreateView
BottomTabFragment1 onActivityCreated
BottomTabFragment1 onResume
BottomTabFragment2 onCreateView
BottomTabFragment2 onActivityCreated
BottomTabFragment3 onCreateView
BottomTabFragment3 onActivityCreated
BottomTabFragment4 onCreateView
BottomTabFragment4 onActivityCreated
BottomTabFragment2 onResume
BottomTabFragment3 onResume
BottomTabFragment4 onResume
//选择 Tab2
BottomTabFragment1 setUserVisibleHint false
BottomTabFragment2 setUserVisibleHint true
//跳过 Tab3 直接选择 Tab4
BottomTabFragment2 setUserVisibleHint false
BottomTabFragment4 setUserVisibleHint true
// 用户回到桌面 再回到当前 APP 打开其余页面当前页面的生命周期也是这样的
BottomTabFragment1 onPause
BottomTabFragment2 onPause
BottomTabFragment3 onPause
BottomTabFragment4 onPause
BottomTabFragment1 onResume
BottomTabFragment2 onResume
BottomTabFragment3 onResume
BottomTabFragment4 onResume
复制代码
能够看出第一次执行 setUserVisibleHint(boolean isVisibleToUser)
除了可见的 Fragment 外都为 false,还能够看出除了这一点不一样之外,全部的 Fragment 都走到了生命周期 onResume 阶段。而选择相邻 tab 的时候已经初始化完成的Fragment 并再也不从新走生命周期方法,只是 setUserVisibleHint(boolean isVisibleToUser)
为 true。当用户进入其余页面的时候全部 ViewPager 缓存的 Fragment 都会调用 onPause 生命周期函数,当再次回到当前页面的时候都会调用 onResume。
能发现这一点,其实对于单层 ViewPager 嵌套 Fragment 可见状态的把握其实已经很明显了。下面给出个人解决方案:
对于 Fragment 可见状态的判断须要设置两个标志位 ,Fragment View 建立完成的标志位 isViewCreated
和 Fragment
第一次建立的标志位 mIsFirstVisible
为了得到 Fragment 不可见的状态,和再次回到可见状态的判断,咱们还须要增长一个 currentVisibleState
标志位,该标志位在 onResume 中和 onPause 中结合 getUserVisibleHint
的返回值来决定是否应该回调可见和不可见状态函数。
整个可见过程判断逻辑以下图所示
接下来咱们就来看下具体实现:
public abstract class LazyLoadBaseFragment extends BaseLifeCircleFragment {
protected View rootView = null;
private boolean mIsFirstVisible = true;
private boolean isViewCreated = false;
private boolean currentVisibleState = false;
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
//走这里分发可见状态状况有两种,1. 已缓存的 Fragment 被展现的时候 2. 当前 Fragment 由可见变成不可见的状态时
// 对于默认 tab 和 间隔 checked tab 须要等到 isViewCreated = true 后才能够经过此通知用户可见,
// 这种状况下第一次可见不是在这里通知 由于 isViewCreated = false 成立,可见状态在 onActivityCreated 中分发
// 对于非默认 tab,View 建立完成 isViewCreated = true 成立,走这里分发可见状态,mIsFirstVisible 此时还为 false 因此第一次可见状态也将经过这里分发
if (isViewCreated){
if (isVisibleToUser && !currentVisibleState) {
dispatchUserVisibleHint(true);
}else if (!isVisibleToUser && currentVisibleState){
dispatchUserVisibleHint(false);
}
}
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// 将 View 建立完成标志位设为 true
isViewCreated = true;
// 默认 Tab getUserVisibleHint() = true !isHidden() = true
// 对于非默认 tab 或者非默认显示的 Fragment 在该生命周期中只作了 isViewCreated 标志位设置 可见状态将不会在这里分发
if (!isHidden() && getUserVisibleHint()){
dispatchUserVisibleHint(true);
}
}
/**
* 统一处理 显示隐藏 作两件事
* 设置当前 Fragment 可见状态 负责在对应的状态调用第一次可见和可见状态,不可见状态函数
* @param visible
*/
private void dispatchUserVisibleHint(boolean visible) {
currentVisibleState = visible;
if (visible) {
if (mIsFirstVisible) {
mIsFirstVisible = false;
onFragmentFirstVisible();
}
onFragmentResume();
}else {
onFragmentPause();
}
}
/**
* 该方法与 setUserVisibleHint 对应,调用时机是 show,hide 控制 Fragment 隐藏的时候,
* 注意的是,只有当 Fragment 被建立后再次隐藏显示的时候才会调用,第一次 show 的时候是不会回调的。
*/
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if (hidden){
dispatchUserVisibleHint(false);
}else {
dispatchUserVisibleHint(true);
}
}
/**
* 须要再 onResume 中通知用户可见状态的状况是在当前页面再次可见的状态 !mIsFirstVisible 能够保证这一点,
* 而当前页面 Activity 可见时全部缓存的 Fragment 都会回调 onResume
* 因此咱们须要区分那个Fragment 位于可见状态
* (!isHidden() && !currentVisibleState && getUserVisibleHint())可条件能够断定哪一个 Fragment 位于可见状态
*/
@Override
public void onResume() {
super.onResume();
if (!mIsFirstVisible){
if (!isHidden() && !currentVisibleState && getUserVisibleHint()){
dispatchUserVisibleHint(true);
}
}
}
/**
* 当用户进入其余界面的时候全部的缓存的 Fragment 都会 onPause
* 可是咱们想要知道只是当前可见的的 Fragment 不可见状态,
* currentVisibleState && getUserVisibleHint() 可以限定是当前可见的 Fragment
*/
@Override
public void onPause() {
super.onPause();
if (currentVisibleState && getUserVisibleHint()){
dispatchUserVisibleHint(false);
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
//当 View 被销毁的时候咱们须要从新设置 isViewCreated mIsFirstVisible 的状态
isViewCreated = false;
mIsFirstVisible = true;
}
/**
* 对用户第一次可见
*/
public void onFragmentFirstVisible(){
LogUtils.e(getClass().getSimpleName() + " ");
}
/**
* 对用户可见
*/
public void onFragmentResume(){
LogUtils.e(getClass().getSimpleName() + " 对用户可见");
}
/**
* 对用户不可见
*/
public void onFragmentPause(){
LogUtils.e(getClass().getSimpleName() + " 对用户不可见");
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater,container,savedInstanceState);
if (rootView == null) {
rootView = inflater.inflate(getLayoutRes(), container, false);
}
initView(rootView);
return rootView;
}
/**
* 返回布局 resId
*
* @return layoutId
*/
protected abstract int getLayoutRes();
/**
* 初始化view
*
* @param rootView
*/
protected abstract void initView(View rootView);
}
复制代码
咱们使以前的 Fragment 改成继承 LazyLoadBaseFragment
打印 log 能够看出:
//默认选中第一 Tab
BottomTabFragment1 setUserVisibleHint false
BottomTabFragment2 setUserVisibleHint false
BottomTabFragment3 setUserVisibleHint false
BottomTabFragment4 setUserVisibleHint false
BottomTabFragment1 setUserVisibleHint true
BottomTabFragment1 onCreateView
BottomTabFragment1 onActivityCreated
BottomTabFragment1 对用户第一次可见
BottomTabFragment1 对用户可见
BottomTabFragment1 onResume
BottomTabFragment2 onCreateView
BottomTabFragment2 onActivityCreated
BottomTabFragment3 onCreateView
BottomTabFragment3 onActivityCreated
BottomTabFragment4 onCreateView
BottomTabFragment4 onActivityCreated
BottomTabFragment2 onResume
BottomTabFragment3 onResume
BottomTabFragment4 onResume
//滑动选中 Tab2
BottomTabFragment1 setUserVisibleHint false
BottomTabFragment1 对用户不可见
BottomTabFragment2 setUserVisibleHint true
BottomTabFragment2 对用户第一次可见
BottomTabFragment2 对用户可见
//间隔选中 Tab4
BottomTabFragment2 setUserVisibleHint false
BottomTabFragment2 对用户不可见
BottomTabFragment4 setUserVisibleHint true
BottomTabFragment4 对用户第一次可见
BottomTabFragment4 对用户可见
// 回退到桌面
BottomTabFragment1 onPause
BottomTabFragment2 onPause
BottomTabFragment3 onPause
BottomTabFragment4 onPause
BottomTabFragment4 对用户不可见
BottomTabFragment1 onStop
BottomTabFragment2 onStop
BottomTabFragment3 onStop
BottomTabFragment4 onStop
// 再次进入 APP
BottomTabFragment1 onResume
BottomTabFragment2 onResume
BottomTabFragment3 onResume
BottomTabFragment4 onResume
BottomTabFragment4 对用户可见
复制代码
上述 log 只演示了如何 ViewPager 中的函数打印,因为 hide show 方法显示隐藏的 Fragment 有人可能认为不须要懒加载这个东西,若是说从建立来讲的确是这样的,可是若是说全部的 Fragment 已经 add 进 Activity 中,此时 Activity 退到后台,全部的 Fragment 都会调用 onPause ,而且在其进入前台的前台统一会回调 onResume, 若是咱们在 Resume 中作了某些操做,那么不可见的 Fragment 也会执行,势必也是个浪费。因此这里的懒加载吧 hide show 的展现方法也考虑进去。
对于无嵌套的 ViewPager ,懒加载仍是相对简单的。可是对于ViewPager 嵌套 ViewPager 的状况可能就出现一些咱们意料不到的状况。
对于双层 ViewPager 嵌套咱们也拿网易云来举例:
能够看出顶层的第二 tab 内部又是一个 ViewPager ,那么咱们试着按照咱们以前的方案打印一下生命周期过程:
BottomTabFragment1 setUserVisibleHint false
BottomTabFragment2 setUserVisibleHint false
BottomTabFragment1 setUserVisibleHint true
BottomTabFragment1 onCreateView
BottomTabFragment1 onActivityCreated
BottomTabFragment1 对用户第一次可见
BottomTabFragment1 对用户可见
BottomTabFragment1 onResume
BottomTabFragment2 onCreateView
BottomTabFragment2 onActivityCreated
BottomTabFragment2 onResume
Bottom2InnerFragment1 setUserVisibleHint false
Bottom2InnerFragment2 setUserVisibleHint false
Bottom2InnerFragment1 setUserVisibleHint true
//注意这里 位于第二个Tab 中的 ViewPager 中的第一个 Tab 也走了可见,而它自己并不可见
Bottom2InnerFragment1 onCreateView
Bottom2InnerFragment1 onActivityCreated
Bottom2InnerFragment1 对用户第一次可见
Bottom2InnerFragment1 对用户可见
Bottom2InnerFragment1 onResume
Bottom2InnerFragment2 onCreateView
Bottom2InnerFragment2 onActivityCreated
Bottom2InnerFragment2 onResume
复制代码
咦奇怪的事情发生了,对于外层 ViewPager 的第二个 tab 默认是不显示的,为何内部 ViewPager 中的 Bottom2InnerFragment1 却走了可见了状态回调。是否是 onActivityCreated 中的写法有问题,!isHidden() && getUserVisibleHint()
getUserVisibleHint() 方法经过 log 打印发如今 Bottom2InnerFragment1 onActivityCreated 时候, Bottom2InnerFragment1 setUserVisibleHint true
的确是 true。因此才会走到分发可见事件中。
咱们再回头看下上述的生命周期的打印,能够发现,事实上做为父 Fragment 的 BottomTabFragment2 并无分发可见事件,他经过 getUserVisibleHint() 获得的结果为 false,首先我想到的是能在负责分发事件的方法中判断一下当前父 fragment 是否可见,若是父 fragment 不可见咱们就不进行可见事件的分发,咱们试着修改 dispatchUserVisibleHint
以下面所示:
private void dispatchUserVisibleHint(boolean visible) {
//当前 Fragment 是 child 时候 做为缓存 Fragment 的子 fragment getUserVisibleHint = true
//但当父 fragment 不可见因此 currentVisibleState = false 直接 return 掉
if (visible && isParentInvisible()) return;
currentVisibleState = visible;
if (visible) {
if (mIsFirstVisible) {
mIsFirstVisible = false;
onFragmentFirstVisible();
}
onFragmentResume();
} else {
onFragmentPause();
}
}
/**
* 用于分发可见时间的时候父获取 fragment 是否隐藏
* @return true fragment 不可见, false 父 fragment 可见
*/
private boolean isParentInvisible() {
LazyLoadBaseFragment fragment = (LazyLoadBaseFragment) getParentFragment();
return fragment != null && !fragment.isSupportVisible();
}
private boolean isSupportVisible() {
return currentVisibleState;
}
复制代码
经过日志打印咱们发现这彷佛起做用了:
BottomTabFragment1 setUserVisibleHint false
BottomTabFragment2 setUserVisibleHint false
BottomTabFragment1 setUserVisibleHint true
BottomTabFragment1 onCreateView
BottomTabFragment1 onActivityCreated
BottomTabFragment1 对用户第一次可见
BottomTabFragment1 对用户可见
BottomTabFragment1 onResume
BottomTabFragment2 onCreateView
BottomTabFragment2 onActivityCreated
BottomTabFragment2 onResume
Bottom2InnerFragment1 setUserVisibleHint false
Bottom2InnerFragment2 setUserVisibleHint false
Bottom2InnerFragment1 setUserVisibleHint true
Bottom2InnerFragment1 onCreateView
Bottom2InnerFragment1 onActivityCreated
Bottom2InnerFragment1 onResume
Bottom2InnerFragment2 onCreateView
Bottom2InnerFragment2 onActivityCreated
Bottom2InnerFragment2 onResume
//滑动到第二个 tab
BottomTabFragment3 setUserVisibleHint false
BottomTabFragment1 setUserVisibleHint false
BottomTabFragment1 对用户不可见
BottomTabFragment2 setUserVisibleHint true
BottomTabFragment2 对用户第一次可见
BottomTabFragment2 对用户可见
BottomTabFragment3 onCreateView
BottomTabFragment3 onActivityCreated
BottomTabFragment3 onResume
复制代码
可是咱们又发现了新的问题,当咱们滑动到第二个 tab 时候,无疑咱们指望获得第二个 tab 中内层 ViewPager 第一个 tab 中 fragment 状态的可见状态,可是从上边的 log 能够发现咱们并无得到其可见状态的打印,问题出当外层 ViewPager 初始化的时候咱们已经经历了 Bottom2InnerFragment1 的初始化,而咱们在 dispatchUserVisibleHint 作了拦截,致使其没法分发可见事件,当其真正可见的时候却发现事件函数并不会再次被调用了。
本着坚信一切困难都是纸老虎的社会主义光荣理念,我404了一下,发现网上极少的嵌套 fragment 懒加载的文章中,大多都采用了,在父 Fragment 可见的时候,分发本身可见状态的同时,把本身的可见状态通知子 Fragment,对于可见状态的 生命周期调用顺序,父 Fragment老是优先于子 Fragment,因此咱们在 Fragment 分发事件的时候,能够在上述拦截子 Fragment 事件分发后,当在父 Fragment 第一可见的时候,通知子 Fragment 你也可见了。因此我再次修改 dispatchUserVisibleHint,在父 Fragment 分发完成本身的可见事件后,让子 Fragment 再次调用本身的可见事件分发方法,此次 isParentInvisible()
将会返回 false ,也就是可见状态将会正确分发。
private void dispatchUserVisibleHint(boolean visible) {
//当前 Fragment 是 child 时候 做为缓存 Fragment 的子 fragment getUserVisibleHint = true
//但当父 fragment 不可见因此 currentVisibleState = false 直接 return 掉
if (visible && isParentInvisible()) return;
currentVisibleState = visible;
if (visible) {
if (mIsFirstVisible) {
mIsFirstVisible = false;
onFragmentFirstVisible();
}
onFragmentResume();
//可见状态的时候内层 fragment 生命周期晚于外层 因此在 onFragmentResume 后分发
dispatchChildVisibleState(true);
} else {
onFragmentPause();
dispatchChildVisibleState(false);
}
}
private void dispatchChildVisibleState(boolean visible) {
FragmentManager childFragmentManager = getChildFragmentManager();
List<Fragment> fragments = childFragmentManager.getFragments();
if (!fragments.isEmpty()) {
for (Fragment child : fragments) {
// 若是只有当前 子 fragment getUserVisibleHint() = true 时候分发事件,并将 也就是咱们上面说的 Bottom2InnerFragment1
if (child instanceof LazyLoadBaseFragment && !child.isHidden() && child.getUserVisibleHint()) {
((LazyLoadBaseFragment) child).dispatchUserVisibleHint(visible);
}
}
}
}
复制代码
dispatchChildVisibleState
方法经过 childFragmentManager 获取当前 Fragment 中全部的子 Fragment 并经过判断 child.getUserVisibleHint()
的返回值,判断是否应该通知子 Fragment 不可见,同理在父 Fragment 真正可见的时候,咱们也会经过该方法,通知child.getUserVisibleHint() = true
的子 Fragment 你可见。
咱们再次打印能够看出通过此次调整内层 Fragment 已经能够准确地拿到本身第一次可见状态了。
BottomTabFragment3 setUserVisibleHint false
BottomTabFragment1 setUserVisibleHint false
BottomTabFragment1 对用户不可见
BottomTabFragment2 setUserVisibleHint true
BottomTabFragment2 对用户第一次可见
BottomTabFragment2 对用户可见
Bottom2InnerFragment1 对用户第一次可见
Bottom2InnerFragment1 对用户可见
BottomTabFragment3 onCreateView
BottomTabFragment3 onActivityCreated
BottomTabFragment3 onResume
复制代码
当我觉得纸老虎一进被我大战胜的时候,我按了下 home 键看了条微信,而后发现 log 打印以下:
BottomTabFragment1 onPause
//Bottom2InnerFragment1 第一不可见回调
Bottom2InnerFragment1 onPause
Bottom2InnerFragment1 对用户不可见
Bottom2InnerFragment2 onPause
BottomTabFragment2 onPause
BottomTabFragment2 对用户不可见
//Bottom2InnerFragment1 第二次不可见回调
Bottom2InnerFragment1 对用户不可见
BottomTabFragment3 onPause
BottomTabFragment1 onStop
Bottom2InnerFragment1 onStop
Bottom2InnerFragment2 onStop
BottomTabFragment2 onStop
BottomTabFragment3 onStop
复制代码
这又是啥状况? 为啥回调了两次,我连微信都忘了回就开始回忆以前分发可见事件的代码,可见的时候时候没问题,为何不可见会回调两次?后来发现问题出如今事件分发的顺序上。
经过日志打印咱们也能够看出,对于可见状态的生命周期调用顺序,父 Fragment老是优先于子 Fragment,而对于不可见事件,内部的 Fragment 生命周期老是先于外层 Fragment。因此第一的时候 Bottom2InnerFragment1 调用自身的 dispatchUserVisibleHint
方法分发了不可见事件,做为父 Fragment 的BottomTabFragment2 分发不可见的时候,又会再次调用 dispatchChildVisibleState
,致使子 Fragment 再次调用本身的 dispatchUserVisibleHint
再次调用了一次 onFragmentPause();
。
解决办法也很简单,还记得 currentVisibleState
这个变量么? 表示当前 Fragment 的可见状态,若是当前的 Fragment 要分发的状态与 currentVisibleState 相同咱们就没有必要去作分发了。
咱们知道子 Fragment 优于父 Fragment回调本方法 currentVisibleState 置位 false,当前不可见,咱们能够当父 dispatchChildVisibleState 的时候第二次回调本方法 visible = false 因此此处 visible 将直接返回。
private void dispatchUserVisibleHint(boolean visible) {
if (visible && isParentInvisible())
return;
// 此处是对子 Fragment 不可见的限制,由于 子 Fragment 先于父 Fragment回调本方法 currentVisibleState 置位 false
// 当父 dispatchChildVisibleState 的时候第二次回调本方法 visible = false 因此此处 visible 将直接返回
if (currentVisibleState == visible) {
return;
}
currentVisibleState = visible;
if (visible) {
if (mIsFirstVisible) {
mIsFirstVisible = false;
onFragmentFirstVisible();
}
onFragmentResume();
//可见状态的时候内层 fragment 生命周期晚于外层 因此在 onFragmentResume 后分发
dispatchChildVisibleState(true);
} else {
onFragmentPause();
dispatchChildVisibleState(false);
}
}
复制代码
对于 Hide And show 方法显示的 Fragment 验证这里讲不在过多赘述,上文也说了,对这种 Fragment 展现方法,咱们更须要关注的是 hide 的时候, onPause 和 onResume 再次隐藏显示的的时候。改方法的验证能够经过下载 Demo 查看 log。Demo 地址
下面是完整 LazyLoadBaseFragment
实现方案:也能够直接戳此下载文件 LazyLoadBaseFragment.java
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.util.List;
/**
* @author wangshijia
* @date 2018/2/2
* Fragment 第一次可见状态应该在哪里通知用户 在 onResume 之后?
*/
public abstract class LazyLoadBaseFragment extends BaseLifeCircleFragment {
protected View rootView = null;
private boolean mIsFirstVisible = true;
private boolean isViewCreated = false;
private boolean currentVisibleState = false;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
if (rootView == null) {
rootView = inflater.inflate(getLayoutRes(), container, false);
}
initView(rootView);
return rootView;
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
// 对于默认 tab 和 间隔 checked tab 须要等到 isViewCreated = true 后才能够经过此通知用户可见
// 这种状况下第一次可见不是在这里通知 由于 isViewCreated = false 成立,等从别的界面回到这里后会使用 onFragmentResume 通知可见
// 对于非默认 tab mIsFirstVisible = true 会一直保持到选择则这个 tab 的时候,由于在 onActivityCreated 会返回 false
if (isViewCreated) {
if (isVisibleToUser && !currentVisibleState) {
dispatchUserVisibleHint(true);
} else if (!isVisibleToUser && currentVisibleState) {
dispatchUserVisibleHint(false);
}
}
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
isViewCreated = true;
// !isHidden() 默认为 true 在调用 hide show 的时候可使用
if (!isHidden() && getUserVisibleHint()) {
dispatchUserVisibleHint(true);
}
}
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
LogUtils.e(getClass().getSimpleName() + " onHiddenChanged dispatchChildVisibleState hidden " + hidden);
if (hidden) {
dispatchUserVisibleHint(false);
} else {
dispatchUserVisibleHint(true);
}
}
@Override
public void onResume() {
super.onResume();
if (!mIsFirstVisible) {
if (!isHidden() && !currentVisibleState && getUserVisibleHint()) {
dispatchUserVisibleHint(true);
}
}
}
@Override
public void onPause() {
super.onPause();
// 当前 Fragment 包含子 Fragment 的时候 dispatchUserVisibleHint 内部自己就会通知子 Fragment 不可见
// 子 fragment 走到这里的时候自身又会调用一遍 ?
if (currentVisibleState && getUserVisibleHint()) {
dispatchUserVisibleHint(false);
}
}
/**
* 统一处理 显示隐藏
*
* @param visible
*/
private void dispatchUserVisibleHint(boolean visible) {
//当前 Fragment 是 child 时候 做为缓存 Fragment 的子 fragment getUserVisibleHint = true
//但当父 fragment 不可见因此 currentVisibleState = false 直接 return 掉
// 这里限制则能够限制多层嵌套的时候子 Fragment 的分发
if (visible && isParentInvisible()) return;
//此处是对子 Fragment 不可见的限制,由于 子 Fragment 先于父 Fragment回调本方法 currentVisibleState 置位 false
// 当父 dispatchChildVisibleState 的时候第二次回调本方法 visible = false 因此此处 visible 将直接返回
if (currentVisibleState == visible) {
return;
}
currentVisibleState = visible;
if (visible) {
if (mIsFirstVisible) {
mIsFirstVisible = false;
onFragmentFirstVisible();
}
onFragmentResume();
dispatchChildVisibleState(true);
} else {
dispatchChildVisibleState(false);
onFragmentPause();
}
}
/**
* 用于分发可见时间的时候父获取 fragment 是否隐藏
*
* @return true fragment 不可见, false 父 fragment 可见
*/
private boolean isParentInvisible() {
Fragment parentFragment = getParentFragment();
if (parentFragment instanceof LazyLoadBaseFragment ) {
LazyLoadBaseFragment fragment = (LazyLoadBaseFragment) parentFragment;
return !fragment.isSupportVisible();
}else {
return false;
}
}
private boolean isSupportVisible() {
return currentVisibleState;
}
/**
* 当前 Fragment 是 child 时候 做为缓存 Fragment 的子 fragment 的惟一或者嵌套 VP 的第一 fragment 时 getUserVisibleHint = true
* 可是因为父 Fragment 还进入可见状态因此自身也是不可见的, 这个方法能够存在是由于庆幸的是 父 fragment 的生命周期回调老是先于子 Fragment
* 因此在父 fragment 设置完成当前不可见状态后,须要通知子 Fragment 我不可见,你也不可见,
* <p>
* 由于 dispatchUserVisibleHint 中判断了 isParentInvisible 因此当 子 fragment 走到了 onActivityCreated 的时候直接 return 掉了
* <p>
* 当真正的外部 Fragment 可见的时候,走 setVisibleHint (VP 中)或者 onActivityCreated (hide show) 的时候
* 从对应的生命周期入口调用 dispatchChildVisibleState 通知子 Fragment 可见状态
*
* @param visible
*/
private void dispatchChildVisibleState(boolean visible) {
FragmentManager childFragmentManager = getChildFragmentManager();
List<Fragment> fragments = childFragmentManager.getFragments();
if (!fragments.isEmpty()) {
for (Fragment child : fragments) {
if (child instanceof LazyLoadBaseFragment && !child.isHidden() && child.getUserVisibleHint()) {
((LazyLoadBaseFragment) child).dispatchUserVisibleHint(visible);
}
}
}
}
public void onFragmentFirstVisible() {
LogUtils.e(getClass().getSimpleName() + " 对用户第一次可见");
}
public void onFragmentResume() {
LogUtils.e(getClass().getSimpleName() + " 对用户可见");
}
public void onFragmentPause() {
LogUtils.e(getClass().getSimpleName() + " 对用户不可见");
}
@Override
public void onDestroyView() {
super.onDestroyView();
isViewCreated = false;
mIsFirstVisible = true;
}
/**
* 返回布局 resId
*
* @return layoutId
*/
protected abstract int getLayoutRes();
/**
* 初始化view
*
* @param rootView
*/
protected abstract void initView(View rootView);
}
复制代码
对于 ViewPager Fragment 懒加载网上文章可能已经不少了,可是对于多层 ViewPager + Fragment 嵌套的文章并非不少,上文还原了我本身对 Fragment 懒加载的探索过程,目前该基类已经应用于公司项目中,相信随着业务的复杂可能有的地方还有可能该方法存在缺陷,若是你们在使用过程当中有问题也请给我留言。
最后感谢 YoKeyword 大神的Fragmentation 提供的思路,一个很好的单 Activity 多 Fragment 库。