Viewpager默认会缓存临近操作的两个页面,也就是至少会缓存一个页面。但我们有时候的需求是需要当滑动到相应页面后再去更新数据,比如网络请求这种,可能你会说,那直接在onResumel里请求数据不就行了,但是ViewPager预加载机制在你处于前一个页面时,已经加载好了下一个页面。当然你也可以将所有页面全部缓存,但这样所消耗的内存不言而喻,而且如果数据过多,第一次进去的时候的速度不可估量。所以这次我们使用懒加载来实现我们的需求。
Fragment 提供了 setUserVisibleHint 方法检测当前碎片是否处于可见状态,但是需要注意的是,这个方法不可以直接回调,因为它与Fragment的生命周期不是同步的。
setOffscreenPageLimit() 方法,缓存的页面数,默认为1。
下面开始我们的操作:
创建一个基类继承与Fragment,并重写其中的 setUserVisibleHint() 与 onActivityCreated() 方法
public abstract class fragment extends Fragment { private boolean isvisible; //与碎片关联的活动创建完毕时调用 @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); isvisible = true; } @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); //是否对用户可见 pan(isVisibleToUser); } public void pan(boolean visble) { if (isvisible && visble) { //等同于onResume()方法 getShow(); } else if (isvisible && !visble) { //等同于onPause()方法 getHint(); } } public abstract void getShow(); public abstract void getHint(); }
用一个Demo来验证一下:
我们先继承Fragment类打印一下日志,然后再继承我们重写的基类。对比一下效果
public class F1 extends Fragment{ @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view=View.inflate(getActivity(),R.layout.f1,null); Log.e("demo","f1"); return view; } }
public class F2 extends Fragment{ @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view=View.inflate(getActivity(),R.layout.f2,null); Log.e("demo","f2"); return view; } }
public class F3 extends Fragment{ @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view=View.inflate(getActivity(),R.layout.f3,null); Log.e("demo","f3"); return view; } }
适配器
public class ViewPagerAdApter extends FragmentStatePagerAdapter { private List<Fragment> list; public ViewPagerAdApter(FragmentManager fm, List<Fragment> list) { super(fm); this.list = list; } @Override public Fragment getItem(int i) { return list.get(i); } @Override public int getCount() { return list.size(); } }
MainActivity
public class MainActivity extends AppCompatActivity { private ViewPager viewPager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); viewPager=findViewById(R.id.viewpager); List<Fragment> list=new ArrayList(); list.add(new F1()); list.add(new F2()); list.add(new F3()); ViewPagerAdApter adApter=new ViewPagerAdApter(getSupportFragmentManager(),list); viewPager.setAdapter(adApter); } }
查看一下Log的打印
可以看到在我们切换到第二个页面时,第三个页面已经被默认加载好了 ,现在我们将子碎片继承自我们的基类,并实现其中的抽象方法。
//另外两个碎片同理 public class F1 extends fragment { @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = View.inflate(getActivity(), R.layout.f1, null); return view; } @Override public void getShow() { Log.e("demo","F1可见"); } @Override public void getHint() { Log.e("demo","F1不可见"); } }
效果如下,可以看到当滑动到第二个页面时,Log处就打出了F1不可见,F2可见。
现在对一些疑点进行分析:
为什么从第三个页面滑动到第二个页面,打印了两次不可见呢?
这是因为Viewpager的缓存机制,默认缓存了N*2+1个页面,所以当你左右两边都有页面时,也就是当前实际有三个页面被缓存。所以当你一旦滑动到第一个页面,默认缓存是1个,所以会销毁掉第三个页面,这时打印的Log就只有一个不可见了。
为什么第一次进来不执行 F1可见呢?
我们在onActivityCreated处打印Log,然后在 setUserVisibleHint处也打印一句Log,观察结果:
这是因为setUserVisibleHint 在Fragment开始前已经调用了,即就是Viewapager.setAdapter之后就已经调用,但是当时还没有初始化完成,我们的子碎片还没有缓存好,所以我们在fragment基类里面定义了一个变量,只有当 onActivityCreated 碎片一定与相关的活动创建完毕的时候再更改变量的值为true,避免空指针的问题,所以第一次进来时没有执行到我们的方法。
另外,默认缓存多少页面,setUserVisibleHint就会执行多少次,在这里,setUserVisibleHint会首先先于Fragment生命周期执行,然后因为默认缓存了当前页面和下一个页面,所以如果给 setUserVisibleHint处打印Log,就会发现,先两次false,再true.,然后再是Fragment的生命周期。如下图