首先看一下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太多或者其布局太复杂,刚实例化时会消耗大量内存和时间,使用时请谨慎!