Fragment大家肯定不会陌生的,几乎每个App里都有它的存在,作为Google在3.0以后引入的一个概念,极大的解决了Activity(或者说手机屏幕)的局限性,让Activity碎片化,正如它的原意 【分段】,【碎片】一样让一个屏幕中的activity展示多个页面成为了现实。
本篇文章主要讲的是在Viewpager和Fragment一起使用的时候出现的一些问题,如何解决。至于Fragment的使用啥的网上有很多说明,这里不去赘述了。
先配一张生命周期图,以便下面分析使用
问题一:当Viewpager和Fragment一起使用的时候,假如有四个Fragment,如果不做其它设置,当Fragment的逻辑复杂耗时的时候或者View结构复杂,在页面进行滑动的时候,可以感觉到明显的卡顿
问题二:当Viewpager和Fragment一起使用的时候,假如有四个Fragment,如果不做其它设置,当你从第一个Fragment滑动到第三个Fragment的时候:
1.如果使用的是FragmentPagerAdapter,那第一个Fragment会执行到onDestroyView,即Fragment的视图被销毁了,实例还存在,当再次滑动到第一个Fragment的时候,会再次从onCreateView回调重建View。
2.如果使用的是FragmentStatePagerAdapter,那第一个Fragment会一直执行到onDetach,即视图销毁了,如果没有添加到回退栈,Fragment的实例也会被销毁,当再次滑动到第一个Fragment的时候,会再从onAttach开始回调。
不管是第一种adapter还是第二种adapter,都对导致Fragment的重复加载。
显然问题一和问题二都不是我们想看到的,有人可能会想通过 ViewPager的 setOffscreenPageLimit 方法预加载四个Fragment,避免第一次 进到页面时候滑动卡顿和重复加载,但是这有一个比较大的问题,如果多个Fragment里有很多的网络请求,耗时操作,那这些操作在同一时间进行操作像View的初始化赋值等还是会出现卡顿问题。
我推荐的做法是通过封装一个Fragment使用延迟加载来避免上述问题,具体原理是使用FragmentPagerAdapter,取消销毁视图,只创建一次View,当Fragment第一次可见时进行数据和View的相关操作。如何封装,见如下代码
/**
* @Description TODO(所有Fragment基类,延迟加载)
* @author mango
* @Date 2018/2/23 17:49
*/
public abstract class BaseFragment extends Fragment {
private String TAG = "BaseFragment";
private View mRoot;
/**
* 是否执行了lazyLoad方法
*/
private boolean isLoaded;
/**
* 是否创建了View
*/
private boolean isCreateView;
/**
* 当从另一个activity回到fragment所在的activity
* 当fragment回调onResume方法的时候,可以通过这个变量判断fragment是否可见,来决定是否要刷新数据
*/
public boolean isVisible;
/*
* 此方法在viewpager嵌套fragment时会回调
* 查看FragmentPagerAdapter源码中instantiateItem和setPrimaryItem会调用此方法
* 在所有生命周期方法前调用
* 这个基类适用于在viewpager嵌套少量的fragment页面
* 该方法是第一个回调,可以将数据放在这里处理(viewpager默认会预加载一个页面)
* 只在fragment可见时加载数据,加快响应速度
* */
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (getUserVisibleHint()) {
onVisible();
} else {
onInvisible();
}
}
/*
* 防止view的重复加载 与FragmentPagerAdapter 中destroyItem方法取消调用父类的效果是一样的
* */
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if(mRoot == null){
mRoot = createView(inflater,container,savedInstanceState);
isCreateView = true;
initView(mRoot);
initListener();
onVisible();
}
return mRoot;
}
protected void onVisible() {
isVisible = true;
if(isLoaded){
refreshLoad();
}
if (!isLoaded && isCreateView && getUserVisibleHint()) {
isLoaded = true;
lazyLoad();
}
}
protected void onInvisible() {
isVisible = false;
}
protected abstract View createView(LayoutInflater inflater,ViewGroup container,Bundle savedInstanceState);
protected abstract void initView(View root);
protected abstract void initListener();
/**
* fragment第一次可见的时候回调此方法
*/
protected abstract void lazyLoad();
/**
* 在Fragment第一次可见加载以后,每次Fragment滑动可见的时候会回调这个方法,
* 子类可以重写这个方法做数据刷新操作
*/
protected void refreshLoad(){}
}
具体作用已经注释很清楚了,子类继承这个就可以了,例如
/**
* @Description TODO()
* @author mango
* @Date 2018/2/23 10:16
*/
public class FirstFragment extends BaseFragment{
public String TAG = "FirstFragment";
public TextView textView;
@Override
public void onAttach(Context context) {
super.onAttach(context);
Log.e(TAG,"onAttach");
}
@Override
protected View createView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
Log.e(TAG,"createView");
View root = inflater.inflate(R.layout.frag_first,container,false);
return root;
}
@Override
protected void initView(View root) {
textView = (TextView) root.findViewById(R.id.tv);
}
@Override
protected void initListener() {
}
@Override
protected void lazyLoad() {
Log.e(TAG,"lazyLoad");
textView.setText("这是第一个fragment");
/**
* 第一次加载的时候在这做数据和View的操作
*/
}
@Override
protected void refreshLoad() {
super.refreshLoad();
}
}
再看看FragmentPagerAdapter
/**
* @Description TODO 使用这种adapter时,滑动viewpager,不可见的fragment最多执行到onDestroyView
* TODO 即视图被销毁了,但fragment实例并没有被销毁,还是常驻内存的,即没有执行到onDestroy,并且保存了Fragment状态
* TODO 具体表现就是该fragment里所实例化的对象依然还在(包括控件),仅仅只是把视图销毁了,fragment的状态依然由FragmentManager维护
* TODO 当再次可见时,仅从onCreateView开始回调,重新创建视图,但像textview和edittext等的值依然保存并显示
* TODO 但是像listview滑动的位置,scrollview滑动的位置等状态并没有保存
* TODO 这种adapter消耗一定的内存,同时避免了频繁的销毁和创建实例 仅适合少量的静态fragment,例如首页的tab分页这种形式
* TODO 重新创建视图可能导致一些数据重复的问题,可以将rootview设置成员变量,判断如果为null,才加载数据或者如baseFragment所写
* @author mango
* @Date 2018/2/23 14:04
*/
public class FragmentAdapter extends FragmentPagerAdapter {
List<Fragment> list ;
FragmentManager fm;
public FragmentAdapter(FragmentManager fm,List<Fragment> list) {
super(fm);
this.list = list;
this.fm = fm;
}
@Override
public Fragment getItem(int position) {
return list.get(position);
}
@Override
public int getCount() {
return list.size();
}
/*
* 重写该方法,取消调用父类该方法
* 可以避免在viewpager切换,fragment不可见时执行到onDestroyView,可见时又从onCreateView重新加载视图
* 因为父类的destroyItem方法中会调用detach方法,将fragment与view分离,(detach()->onPause()->onStop()->onDestroyView())
* 然后在instantiateItem方法中又调用attach方法,此方法里判断如果fragment与view分离了,
* 那就重新执行onCreateView,再次将view与fragment绑定(attach()->onCreateView()->onActivityCreated()->onStart()->onResume())
* */
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
// super.destroyItem(container, position, object);
}
}
这就是ViewPager+Fragment组合使用采用延迟加载避免上诉问题的例子