整个原理是把真实需要加载的Fragment包装到一个空LazyFragment里,通过控制LazyFragment来实现对真实Fragment的加载。具体效果就是需要惰性加载的类不需要改继承类,基本不用做任何修改,用LazyFragment包装下就能用。不关心具体原理的话请直接参看源码:
https://github.com/aesean/LazyLoadFragment
Fragment惰性算是非常常用的一个功能了。之前我们惰性加载也只是用在ViewPager里,然后配合setUserVisibleHint可以实现惰性加载。如果用Google搜索的话可能会找到类似下面的实现。
实现原理都是通过setUserVisibleHint回调,然后在onCreateView做判断选择加载的布局。但是注意setUserVisibleHint是需要用户自己主动去处理的,Fragment本身是不会去维护它的。也就是说除非有类似FragmentPagerAdater里的方式那样主动去维护之外,getUserVisibleHint永远都是返回true。
@Override
public Object instantiateItem(ViewGroup container, int position) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
final long itemId = getItemId(position);
// Do we already have this fragment?
String name = makeFragmentName(container.getId(), itemId);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
mCurTransaction.attach(fragment);
} else {
fragment = getItem(position);
if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), itemId));
}
if (fragment != mCurrentPrimaryItem) {
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
}
return fragment;
}
搜到的文章基本都是通过复写setUserVisibleHint,然后通过接受到的参数在onCreateView里处理加载。这样当然是可以实现的。但是这种处理方式有个很麻烦的问题。这需要所有需要有类似特性的Fragment需要继承写好的基类。通常这可能没什么问题,除了一些类似DialogFragment这种特殊情况之外。另外Fragment的生命周期会受到影响。可能很多人会通过Fragment的onViewCreate来初始化控件引用。这种写法会有个很麻烦的问题就是需要判断当前是惰性加载还是正常加载。因为惰性加载和正常加载时候的View是不一样的。然后相当于是Fragment所有的生命周期可能都需要判断当前是惰性加载还是正常加载了。如果再加上需要处理的类非常多的话,这里就要非常头疼了。
那有没有一种简便的实现方式?最好是以前写好的类不做任何改动,直接就能继续用的。
我们其实只是希望某个Fragment实现惰性加载,那我们让目标Fragment不动,在需要加载这个Fragment的地方先加载一个写好的空Fragment,等需要的时候在load真实的Fragment,这样原Fragment就无需做任何修改了。
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mOnCreateView = true;
return inflater.inflate(R.layout.fragment_lazy, container, false);
}
private void clearRootContainer() {
mLazyRootContainer.removeAllViews();
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mLazyRootContainer = (ViewGroup) view.findViewById(R.id.lazy_root_container);
if (isFirstVisible && getUserVisibleHint()) {
loadLazyFragment();
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
mOnCreateView = false;
isFirstVisible = true;
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (mOnCreateView && getUserVisibleHint() && isFirstVisible) {
loadLazyFragment();
}
}
private void loadLazyFragment() {
clearRootContainer();
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
transaction.replace(R.id.lazy_root_container, getLazyFragmentLoader());
transaction.commit();
isFirstVisible = false;
}
默认加载的空Fragment如果你需要可以添加一些进度条之类的。由于LazyFragment需要知道自己要真实加载的Fragment是什么。所以这里创建一个接口
public interface ILazyFragmentLoader {
Fragment createFragment(Context context);
}
然后把这个接口通过Bundle参数传递进来。通过Bundle传递就必须实现序列化接口。
static Bundle createBundle(Bundle bundle, ILazyFragmentLoader target) {
if (bundle == null) {
bundle = new Bundle();
}
if (target instanceof Parcelable) {
bundle.putParcelable(KEY_LAZY_FRAGMENT_LOADER, (Parcelable) target);
} else if (target instanceof Serializable) {
bundle.putSerializable(KEY_LAZY_FRAGMENT_LOADER, (Serializable) target);
} else {
throw new IllegalArgumentException("ILazyFragmentLoader 必须实现Serializable或者Parcelable");
}
return bundle;
}
这里其实实现Parcelable和Serializable中的一个就可以了。这里注意bundle只实现了Parcelable接口,没有实现Serializable,所以如果自定义的ILazyFragmentLoader实现类如果需要用到Bundle,一定要实现Parcelable。
public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
......
}
修改后的最终效果就是
public static LazyFragment newLazyInstance(String title) {
return LazyFragment.newInstance(new LazyFragmentLoader(SampleFragment.class, newArguments(title)));
}
SampleFragment.class是真实需要处理的Fragment,newArguments是个Bundle对象是需要给SampleFragment传递的参数。SampleFragment被包装成了LazyFragment,然后把LazyFragment当成SampleFragment使用即可。如果SampleFragment被用在FragmentViewPager里这里就会自动实现惰性加载,因为FragmentViewPager处理了setUserVisibleHint。如果是希望手动控制加载处理setUserVisibleHint方法即可。具体可以参考ClickLoadLazyFragment的代码。