项目中使用TabHost+ViewPager实现横划切换和顶部Tab导航切换。总共有三个Tab Fragment,发现从Tab1切换到Tab2时,不是非常流畅,而从Tab2切换回Tab1却没有问题。同样在Tab3切换到Tab2时也会卡一下。

最后发现是ViewPager自带的缓存策略引起的。ViewPager定义了一个私有属性来保存当前缓存Page个数,默认为1(其实是左右两边各一个,后边会解释)。

private static final int DEFAULT_OFFSCREEN_PAGES = 1;
private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES;

默认情况下,初始化进入Tab1时,ViewPager为了保证滑动顺畅会自动加载好Tab2页面,否则在横划切换时会因Page2未初始化导致不流畅或者失败都有可能。从Tab1切换至Tab2时,ViewPager会继续去加载Tab2页面的下一个Page,即Tab3页面,这就解释了滑动不流畅的原因,但至少可以保证滑动切换功能。

当从Tab2切换到Tab3时,滑动流畅,因为没有下一个页面需要提前加载。但是,此时,ViewPager会销毁Tab1页面(快速切换回Tab1可以发现Tab1重现加载),导致从Tab3切换回Tab2仍然发生卡顿现象,因为默认缓存页面个数为1。

注意,在Tab2页面切换回Tab1页面时,其实也是流畅的,说明Tab1并没有销毁,这也说明ViewPager默认其实缓存的是左右两边各1个页面。

找到原因,修改就比较简单了。根据需求,如果只是想滑动流畅,可以通过设置setOffScreenPageLimit(2)解决问题。

如果想初始化时只加载当前页面,用户滑动或者点击Tab时才加载页面,可能有些同学会想setOffScreenPageLimit(0)不就可以了吗?很可惜,ViewPager不允许你这样做。

public void setOffscreenPageLimit(int limit) {
   if (limit < DEFAULT_OFFSCREEN_PAGES) {
      Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " + DEFAULT_OFFSCREEN_PAGES);
      limit = DEFAULT_OFFSCREEN_PAGES;
    }
    if (limit != mOffscreenPageLimit) {
            mOffscreenPageLimit = limit;
            populate();
    }
 }

从代码中可以看出,如果limit小于1,会恢复为默认值1。

如果仍然想实现上述功能,可以考虑在Tab页面Fragment的visibility为可见状态时才去请求加在真正的数据,在不可见状态时加载一个“假”的页面,通常可以为一个Loading页面。Fragment的可见性可以通过重写setUserVisibleHint方法获得。例如:

private boolean isDataLoaded;
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    if(isVisibleToUser && !isDataLoaded) {         
        // .... 触发网络请求,加载数据

        isDataLoaded = true;
    }
}

setOffscreenPageLimit(2)仍然设置为2。这样,不但可以保证页面横划切换流畅,而且只有用户真正切换到下一个页面才会加载页面的真实内容,从而达到节省流量等资源的效果。