我的Fragment可见性我做主!
问题分析:
App结构容易出现以下结构:
上图是一个简易的Fragment嵌套示意图,实际的远要比这个复杂。现在有这样一个问题,所有的Fragment都需要根据自己的可见性来进行一些业务逻辑处理,如需要在可见的情况下才进行倒计时控件的刷新,系统也有提供一些Api供使用,先分析一下这些方法都有什么缺点。
方案1:在生命周期函数onResume(),onPause()去设置可见性:
@Override
public void onResume() {
super.onResume();
mVisible = true;
}
@Override
public void onPause() {
super.onPause();
mVisible = false;
}
这样存在问题,当使用ViewPager去管理Fragment时,在Fragment之间进行切换,以上两个生命周期函数是不会调用的。
方案2:监听onHiddenChanged(boolean hidden)回调
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if(hidden){
mVisible = false;
} else {
mVisible = true;
}
}
这个方法的回调是有条件的,那就是当使用FragmentTransaction.hide(),FragmentTransaction.show()时才会回调
方案3:复写Fragment的setUserVisibleHint(boolean isVisibleToUser)方法
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if(isVisibleToUser){
mVisible = true;
} else {
mVisible = false;
}
}
该方法的调用前提是,使用ViewPager + FragmentPagerAapter,当切换Fragment的时候,会调用该方法。那么会存在一个问题,当我们跳转到另外一个Activity时,由于没有通过ViewPager去切换Fragment,所以该方法不会触发。
方案4:手动去调用对应的回调方法。把控制权掌握在自己手中,比如当从Fragment1切换到Fragment2时(不论是使用哪种方式进行切换),手动调用setUserVisibleHint(boolean isVisibleToUser)或者onHiddenChanged(boolean hidden)方法:
fragment1.setUserVisibleHint(false);
List<Fragment> fragments = fragment1.getChildFragmentManager();
for (Fragment f : fragments) {
f.setUserVisibleHint(false);
}
fragment2.setUserVisibleHint(true);
fragment3.setUserVisibleHint(false);
通过手动调用系统的方法,能够在更加细粒度的层次上控制嵌套Fragment的可见性,但还是会有一些问题,有时候setUserVisibleHint或者onHiddenChanged存在重复调用的情况,因为系统也在调该方法,我们自己也在调用该方法。下边给出最终的解决方案,基本思想是基于方案4的。
最终方案:不再依赖系统提供的几个方法,采用拦截器模式把可见性的传递流程封装到BaseFragment中。这里仿照Android中View的触摸事件的传递过程,添加了对应的方法:
public abstract class BaseFragment extends Fragment {
protected boolean mIsVisible;
//是否对可见性事件进行拦截,默认不拦截
protected boolean interceptVisibilityEvent() {
return false;
}
//可见性事件的过滤规则
protected List<Boolean> filterVisibilityEvent(boolean visible) {
return null;
}
protected void dispatchVisibility(boolean visible) {
//具体的事件分发逻辑
}
//暴露给外部调用者的方法,把可见性的控制权限交给用户
@MainThread
public void setVisible(boolean visible) {
if (visible != mIsVisible) {
dispatchVisibility(visible);
}
}
}
先分析一下事件分发的逻辑:
protected void dispatchVisibility(boolean visible) {
mIsVisible = visible;
if (!interceptVisibilityEvent()) {
if (isAdded()) {
FragmentManager manager = getChildFragmentManager();
List<Fragment> fragments = manager.getFragments();
for (int i = 0; i < fragments.size(); i++) {
Fragment f = fragments.get(i);
if (f instanceof BaseFragment) {
BaseFragment bf = (BaseFragment) f;
List<Boolean> filter = filterVisibilityEvent(visible);
if (filter == null) {
if (!visible) {
bf.dispatchVisibility(false);
} else {
if (i == 0) {
bf.dispatchVisibility(true);
} else {
bf.dispatchVisibility(false);
}
}
} else {
bf.dispatchVisibility(filter.get(i));
}
}
}
}
}
}
首先调用interceptVisibilityEvent()判断是否需要拦截,需要拦截,则事件传递到此,后续子Fragment不再收到通知。不拦截的情况遍历子Fragment,每次迭代过程中通过调用filterVisibilityEvent()方法获取具体的分发策略,根据不同的业务逻辑去复写该方法即可,默认是返回null,提供了一个默认的事件传递的逻辑:当前Fragment不可见,那么子Fragment都不可见;当前Fragment可见时,默认把第一个添加的子Fragment设为可见,其余的都设为不可见。
接下来看一下怎么根据具体的业务逻辑来实现不同的事件分发,在Fragment1中重写filterVisibilityEvent()方法:
@Override
protected List<Boolean> filterVisibilityEvent(boolean visible) {
List<Boolean> visibilities = null;
if (visible) {
if (f1Radio != null) {
visibilities = new ArrayList<>(2);
if (f1Radio.isChecked()) {
visibilities.add(0, true);
visibilities.add(1, false);
} else {
visibilities.add(0, false);
visibilities.add(1, true);
}
}
} else {
visibilities = new ArrayList<>(2);
visibilities.add(0, false);
visibilities.add(1, false);
}
return visibilities;
}
f1Radio就是一个RadioButton,逻辑也很简单,对应的RadioButton被选中后,将可见性事件分发给对应的Fragment即可。这里List在添加时,下标对应Fragment被关联时候的下标。
有了对Fragment的可见性的控制,还可以方便的实现Fragment的懒加载。默认情况BaseFragment中的mIsVisible为false,当在初始化Fragment的时候,通过手动调用setVisible(true),来控制哪些Fragment需要加载数据。在BaseFragment中进行一个数据加载的判断:
public abstract class BaseFragment extends Fragment {
protected boolean mIsDataLoaded;
protected boolean mIsVisible;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (mIsVisible && !mIsDataLoaded) {
initData();
mIsDataLoaded = true;
}
}
@MainThread
public void setVisible(boolean visible) {
if (mIsVisible && !mIsDataLoaded) {
initData();
mIsDataLoaded = true;
}
}
protected abstract void initData();
}
}
如有理解不当之处,欢迎大家一起交流讨论!
参考文章:
【1】https://www.cnblogs.com/dongweiq/p/5411899.html