让ViewGroup中Fragment可见时才加载和不重复加载的方法

首先看一下Fragment的“setUserVisibleHint()”和“getUserVisibleHint()”方法,下面是个测试例子(1):

Fragment——F3的部分代码:
……
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
  Log.i("F3", "setUserVisibleHint "+isVisibleToUser);
  super.setUserVisibleHint(isVisibleToUser);
}

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
  Log.i("F3", "onCreate");
  super.onCreate(savedInstanceState);
}

@Override
public void onDestroy() {
  Log.i("F3", "onDestroy");
  super.onDestroy();
}
……

假设现在ViewGroup有三个相邻的Fragment分别为F1、F2、F3(主要代码如上),只在F3的相应方法中加上打印Log的语句,测试结果:
首先进入ViewGroup的界面,我们看到的当然是F1,同时也实例化了F2,F3还没实例化。
点击进入F2,预加载F3,打出Log:“setUserVisibleHint false”和“onCreate”
点击F3,打印:“setUserVisibleHint true”
点击F2:“setUserVisibleHint false”
点击F1:“onDestory”
点击F3:“setUserVisibleHint false”和“setUserVisibleHint true”
点击F1:“setUserVisibleHint false”和“onDestory”

总结:Fragment的API介绍,getUserVisibleHint()返回的变量,是App提供的代表此Fragment当前对于用户是否可见的一个布尔值;
而例子也证明了在实例化Fragment对象时,setUserVisibleHint()方法会在onCreate()前面被调用,在我们的认识中Fragment的生命周期都是从onCreate()方法开始的,而父容器或者说是上一级的上下文对象却在Fragment的生命周期“开始之前”就将它的isVisibleToUser值设置好了,因此,我们可以在onCreate()方法或者之后的onCreateView()、onStart()等方法的里面,根据需要去调用getUserVisibleHint()方法判断Fragment可见性,再根据需要是否加载数据。
注意:setUserVisibleHint()方法是在onCreate()方法、甚至是onAttach()方法之前调用的,所以它里面不能有任何对Context上下文对象的调用!

下面是自己完成的一个,只在界面第一次可见时加载数据,的Fragment例子(2):
public class MessageFragment extends Fragment {
    private ListView msgLv;
    private ServiceAdapter msgAdapter;

    /**
     * 是否在onStart()方法中发出网络请求,默认在onStart()中
     */
    private boolean onStartGetNetData = true;
    /**
     * 是否已经发出网络请求
     */
    private boolean hasGotNetData = false;

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        if(isVisibleToUser && !onStartGetNetData && !hasGotNetData){
            //界面可见、还未请求数据并且不是由onStart()请求数据时执行
            if(/*判断网络*/){
                getNetWorkData();//请求网络数据的方法
                hasGotNetData = true;
            }else{
                //用Toast提示没有连接网络
            }
        }
        super.setUserVisibleHint(isVisibleToUser);
    }


    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_msg_service, container, false);
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        msgLv = (ListView) view.findViewById(R.id.msg_lv);
        msgAdapter = new ServiceAdapter(getActivity());
        msgLv.setAdapter(msgAdapter);
    }

    @Override
    public void onStart() {
        if(getUserVisibleHint() && onStartGetNetData && !hasGotNetData){
            //界面可见并从未请求过数据时加载数据
            if(/*判断网络的方法*/){
                getNetWorkData();//请求网络数据的方法
                hasGotNetData = true;
            }else{
                //Toast提示没有连接网络
            }
        }else if(onStartGetNetData){
            onStartGetNetData = false;//界面不可见并且是由onStart()加载数据时置为false,委托给setUserVisibleHint()去请求数据
        }
        super.onStart();
    }

    @Override
    public void onDestroy() {
        hasGotNetData = false;
        onStartGetNetData = true;//Fragment的状态会被所属的Activity保存,但不知能保存多久,保险起见变量设为初值
        super.onDestroy();
    }
}

 下面说说为什么要这样写: 
  
首先看看ViewGroup滑动时,Fragment的一些周期函数调用顺序(借用上面三个Fragment的例子):
从第一个Fragment滑到第三个、创建对象时:[ setInitialSavedState(SavedState state) 第二次创建Fragment时才会调用 ]  --> 
setUserVisibleHint() --> onAttach() --> onCreate() --> onCreateView() -->onViewCreated()
--> onViewStateRestored() --> onStart() --> onResume()
从第三个Fragment滑到第一个、对象销毁时:
[ setUserVisibleHint() 先跳到ViewPager其它Fragment时才会有,如果直接按Home键时不会被调用 ] --> 
onSaveInstanceState() --> onPause() --> onStop() --> onDestroyView() --> onDetach()

结合测试例子(1)分析:
如果我们从F1直接点击显示F3,setUserVisibleHint()方法会被连续调用两次,第一次为不可见,第二次为可见,随后就调用了onCreate()和onStart()等方法。这种情况下好像在setUserVisibleHint()和onStart()方法里都可以进行加载网络,然而没有这么简单。前者:在setUserVisibleHint()方法里不能有任何对Context的引用,因为它还没有调用onAttach()方法,也就是说这里不能通过上下文对象的getSystemService方法获取系统服务、不能得到是否连网;而且界面元素都未初始化,不能设置数据,还可能遇到空指针异常。后者:在onStart()方法里去请求网络的话,当你用Home退出后再进入应用,它又会被调用,也就会再次加载网络。
如果先从F1滑到F2,F3的onCreate()方法已经被调用,也就是onStart()也会被调用而这时界面还不可见也就不会加载网络,等我们再从F2跳到F3时,onStart()方法已经不会被调用,那就没数据了?!
所以现在的需求是:从F1直接跳到F3界面时,由onStart()方法加载网络;从F1滑到F2再滑到F3时,由setUserVisibleHint()方法加载网络。

因此本人添加了onStartGetNetData变量来判断是否由onStart()方法调用网络请求的方法,再添加一个hasGotNetData变量来判断是否已经获取过一次数据。

现在数据只加载一次了,至于怎么刷新,自己再加上一个下拉刷新就OK了。方法简单,语言简陋,如有更好的方法还请赐教。

最后提一下:ViewPager.setOffscreenPageLimit(int number)方法是设置所看到的View周围有多少个View的状态会被保存着,也就是说当我们见到的第一个Fragment被实例化时,它左边和右边的number个Fragment也会被实例化。这个方法虽然能帮我们更简单的实现数据只加载一次,但是如果ViewPager里面的View太多或者其布局太复杂,刚实例化时会消耗大量内存和时间,使用时请谨慎!

你可能感兴趣的:(android,应用)