ViewPager+Fragments+LazyLoad

ViewPager+Fragments可以帮助Android开发者方便地实现多个tab页之间的切换。可是在使用中你有没有遇到过一些不是那么美丽的状况呢?比方说,进入页面的前几秒钟特别卡顿?从别的页面切换回来,竟然重新加载了页面?从网上Copy了LazyLoad代码下来,来回切换了几次崩溃了?WTF!!!ViewPager+Fragments用起来并不复杂,不消十分钟你就可以把它引入项目,但是要想获得好的体验,就要多花一点功夫了。这篇文章,我就记录一下自己在开发过程中使用到的优化方法。

LazyLoad

首先我们来复习一下ViewPager的基础知识(使用FragmentAdapter)。ViewPager默认至多预加载2个Fragment(可见页面称为VisibleFragment,可见页面左右各一个InvisibleFragment),至少预加载一个页面(可见页面是第一个,仅在右侧有一个InvisibleFragment)。也就是说,我们第一次进入页面时,ViewPager至少加载了两个Fragment,一个VisibleFragment,一个InvisibleFragment。如果InvisibleFragment有什么耗时操作,就会连累VisibleFragment特别卡顿。

聪明如你肯定会想,这个问题so easy,调用一下ViewPager.setOffscreenPageLimit(0),禁止ViewPager预加载不就ok了嘛!naive!

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();
      }
  }

ViewPager.setOffscreenPageLimit(int limit)方法的源码,可以看到预加载页面至少是DEFAULT_OFFSCREEN_PAGES,这个值是1。

你可能又会想了,InvisibleFragment页面从不可见转为可见,这个状态肯定能够捕捉到吧,在页面变为可见的时候执行耗时操作不就可以了嘛!对头!!!这个方法就是Fragment.setUserVisibleHint(boolean isVisibleToUser),一旦Fragment可见状态发生变化,这个方法就会被调用。所以我们就可以这样写:

/**
 * 是否已加载数据
 */
private boolean mLoaded = false;

/**
 * 一旦页面可见性发生变化,这个方法就会被调用
 * 当页面可见且数据未加载时,加载数据
 * @param isVisibleToUser
 */
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    if (isVisibleToUser && !mLoaded) {
        mLoaded = true;
        loadData();
    }
}

这个就是所谓的LazyLoad!

ViewPager中的Fragment

学到上面的新技能之后你兴冲冲地去实验,编码>>运行>>不错呀!写博客的小哥没骗我!>>纳尼!崩溃了!这个死骗子,肯定拿假的LazyLoad来骗我,看我不骂死他!看官大爷请息怒!我真没骗你,不信咱们接着往下看!

这个地方的崩溃应该是NullPointerException,提示在View初始化之前即对其进行了操作。看来setUserVisibleHint()方法被调用时,onCreateView()方法还没有被调用。要解决这个问题我们要首先弄清楚Fragment中相关方法被调用的顺序(你可以在自己项目中把方法的调用都打印出来)。

然后我们发现Fragment中相关方法的调用顺序是这个样子的:
setUserVisibleHint()>>onCreateView()>>onActivityCreated()。

setUserVisibleHint()确实是在onCreateView()之前被调用的,这可怎么办呀?表怂呀!这还能难得住你?添加一个全局变量,标识onCreateView()方法是否被调用,如果没有被调用,setUserVisibleHint()不执行加载数据的操作。然后我们还需要在onActivityCreated()里面二次判断是否首次加载。现在代码长这个样子:

/**
 * onCreateView()是否被调用
 */
private boolean mOnCreateViewInvoked = false;
/**
 * 是否已加载数据
 */
private boolean mLoaded = false;

/**
 * 一旦页面可见性发生变化,这个方法就会被调用
 * 当onCreateView()已经被调用&&页面可见&&数据未加载时,加载数据
 * @param isVisibleToUser
 */
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    if (mOnCreateViewInvoked && isVisibleToUser && !mLoaded) {
        mLoaded = true;
        loadData();
    }
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    mOnCreateViewInvoked = true;
    // your code
}

/**
 * 二次测试是否首次加载
 * @param savedInstanceState
 */
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    if (getUserVisibleHint() && !mLoaded) {
        mLoaded = true;
        loadData();
    }
}

你再试试,现在是不是不崩溃了!我跟你讲了,我写的是真的LazyLoad!

一点小扩展

刷了一会儿你又发现问题了,从别的页面返回之后咋又重新加载数据了?在tab页中,我们可能并不希望,页面每次可见的时候数据都重新加载一遍,而是只在第一次可见的时候加载。看官大爷您这个需求比较简单,只需要一行代码就可以搞定了。我们只需要在ViewPager初始化的时候添加一行设置ViewPager.setOffscreenPageLimit(sizeOfFragments)。这样ViewPager中所有的Framgment都不会被回收了,陪你到天荒地老!

接下来写一篇文章介绍一下两个ViewPager嵌套时,Fragment可见性的判断。

完!

你可能感兴趣的:(ViewPager+Fragments+LazyLoad)