Android之ViewPager嵌套Fragment卡顿解决方法 避免Viewpager滑动重复加载Fragment Fragment延迟加载

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组合使用采用延迟加载避免上诉问题的例子

猜你喜欢

转载自blog.csdn.net/qq_30993595/article/details/80736814