我的Fragment可见性我做主!

问题分析:
App结构容易出现以下结构:

Fragment可见性精准控制方案

  上图是一个简易的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

相关文章:

  • 2022-12-23
  • 2022-12-23
  • 2021-11-26
  • 2021-10-13
  • 2021-11-16
  • 2021-11-27
猜你喜欢
  • 2022-12-23
  • 2021-10-10
  • 2021-06-29
  • 2021-05-25
  • 2022-12-23
  • 2021-08-06
  • 2021-06-10
相关资源
相似解决方案