27
2017
09

Day3(上) 一篇文章带你吃透ViewPager的三大用法及刷新问题

今天我们来一起实现“爱阅”首页滑动切换分类浏览阅读的效果,并将ViewPager和TabLayout结合起来用以实现顶部导航栏的分类展示,并增加点击快速切换分类的功能。ViewPager+TabLayout也是当前最炙手可热的组合方式,我会首先对基础的知识点做一些讲解,然后对我们今天的内容进行实现。

首先看一下效果图:

点此进入目录:[干货] 十天 教你从创意到上线APP

1、ViewPager简介

ViewPager是android扩展包v4包中的类,这个类可以让用户左右切换当前的view
- ViewPager类直接继承了ViewGroup类,所以它是一个容器类,可以在其中添加其他的view类。
- ViewPager类需要一个PagerAdapter适配器类给它提供数据。
- ViewPager经常和Fragment一起使用,并且提供了专门的FragmentPagerAdapter和FragmentStatePagerAdapter类供Fragment中的ViewPager使用。

2、ViewPager的适配器

上文的简介中提到了PagerAdapter,其实和RecyclerView、ListView等控件使用一样,ViewPager需要设置PagerAdapter来完成页面和数据的绑定,这个PagerAdapter是一个基类适配器,我们经常用它来实现App引导图,它的子类有FragmentPagerAdapter和FragmentStatePagerAdapter,这两个子类适配器用于和Fragment一起使用,在安卓应用中它们就像RecyclerView一样出现的频繁。

(1)实现一个最基本的PagerAdapter
    public class AdapterViewpager extends PagerAdapter {
        private List<View> mViewList;

        public AdapterViewpager(List<View> mViewList) {
            this.mViewList = mViewList;
        }

        @Override
        public int getCount() {//必须实现
            return mViewList.size();
        }

        @Override
        public boolean isViewFromObject(View view, Object object) {//必须实现
            return view == object;
        }

        @Override
        public Object instantiateItem(ViewGroup container, int position) {//必须实现,实例化
            container.addView(mViewList.get(position));
            return mViewList.get(position);
        }

        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {//必须实现,销毁
            container.removeView(mViewList.get(position));
        }
    }
  • instantiateItem()
    可以看到instantiateItem()做了两件事,第一:将当前视图添加到container中,第二:返回当前View。也就是说instantiateItem()的功能是创建指定位置的页面视图,并且适配器有责任增加即将创建的View视图添加到这里给定的container中。它的返回值代表新增视图页面的Object(Key),这里没必要非要返回视图本身,也可以返回可以代表当前页面的任意值,只要你可以与你增加的View一一对应即可,比如position变量也可以做为Key。

  • isViewFromObject()
    该函数用来判断 instantiateItem() 函数所返回来的 Key 与一个页面视图是否是代表的同一个视图(即它俩是否是对应的,对应的表示同一个 View),如果对应的是同一个View回 true,否则返回 false。

  • destroyItem()
    该方法的功能是移除一个给定位置的页面,适配器有责任从容器中删除这个视图,这是为了确保在 finishUpdate(viewGroup) 返回时视图能够被移除。

不过说道destroyItem()这个方法的时候就不得不提及ViewPager的刷新问题了,因为ViewPager的刷新并不是我们最初想的调用一下notifyDataSetChanged()就完事了这么简单的,我们会在后文说明“爱阅”中遇到的坑和解决办法。

(2)实现一个最基本的FragmentPagerAdapter
/** * Created by : WGH. */
public class ViewPagerAdapter extends FragmentPagerAdapter {
    private ArrayList<Category> mCategoryList;
    private Context mContext;
    private Fragment mFragment;

    public ViewPagerAdapter(FragmentManager fm, Context context) {
        super(fm);
        mContext = context;
        mCategoryList = DataCacheHelper.getInstance().getPagerChildCategories();
    }

    public void onCategorysChange(String key) {
        if (key != null) {
            mCategoryList = DataCache.getInstance().getChildCategorys(key);
            notifyDataSetChanged();
        } else {
            DLog.e("onCategorysChange() Error!");
        }
    }

    @Override
    public Fragment getItem(int position) {// 必须实现
        return ViewPagerFragment.newInstance(mContext, mCategoryList.get(position));
    }

    @Override
    public int getCount() {// 必须实现
        if (mCategoryList != null) {
            return mCategoryList.size();
        } else {
            return 0;
        }
    }

    @Override
    public CharSequence getPageTitle(int position) {// 选择性实现
        return mCategoryList.get(position).getName();
    }

    public String getFragmentTag(int viewPagerId, int fragmentPosition) {
        return "android:switcher:" + viewPagerId + ":" + fragmentPosition;
    }

    public String getFragmentTag(int viewPagerId, int fragmentPosition) {
        return "android:switcher:" + viewPagerId + ":" + fragmentPosition;
    }

    @Override
    public int getItemPosition(Object object) {
        return POSITION_NONE;
    }
}

FragmentStatePagerAdapter的实现和FragmentPagerAdapter的实现一样就不在写了。而这里的getItemPosition()之所以这样写同样和ViewPager的刷新有关,我们后文讲解。

三个适配器的区别:

PagerAdapter是基类适配器是一个通用的ViewPager适配器,相比PagerAdapter,FragmentPagerAdapter和FragmentStatePagerAdapter更专注于每一页是Fragment的情况,而这两个子类适配器使用情况也是有区别的。FragmentPagerAdapter适用于页面比较少的情况,FragmentStatePagerAdapter适用于页面比较多的情况,我们可以从两个适配器的源码得知。

  • FragmentStatePagerAdapter
@Override
  public Object instantiateItem(ViewGroup container, int position) {
      if (mFragments.size() > position) {
          Fragment f = mFragments.get(position);// fragment被释放后这里得到的null值
          if (f != null) {
              return f;
          }
      }

      if (mCurTransaction == null) {
          mCurTransaction = mFragmentManager.beginTransaction();
      }

      Fragment fragment = getItem(position);// fragment被释放后或者是初次进入页面拿到新的Fragment实例
      if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
      if (mSavedState.size() > position) {
          Fragment.SavedState fss = mSavedState.get(position);
          if (fss != null) {
              fragment.setInitialSavedState(fss);
          }
      }
      while (mFragments.size() <= position) {
          mFragments.add(null);
      }
      fragment.setMenuVisibility(false);
      fragment.setUserVisibleHint(false);
      mFragments.set(position, fragment);
      mCurTransaction.add(container.getId(), fragment);// 新的Fragment实例 是add上去的

      return fragment;
  }

 @Override
  public void destroyItem(ViewGroup container, int position, Object object) {
      Fragment fragment = (Fragment) object;

      if (mCurTransaction == null) {
          mCurTransaction = mFragmentManager.beginTransaction();
      }
      if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
              + " v=" + ((Fragment)object).getView());
      while (mSavedState.size() <= position) {
          mSavedState.add(null);
      }
      mSavedState.set(position, fragment.isAdded()
              ? mFragmentManager.saveFragmentInstanceState(fragment) : null);
      mFragments.set(position, null);// 真正释放了fragment实例

      mCurTransaction.remove(fragment);
  }
  • FragmentPagerAdapter
    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }

        final long itemId = getItemId(position);

        // Do we already have this fragment?
        String name = makeFragmentName(container.getId(), itemId);
        Fragment fragment = mFragmentManager.findFragmentByTag(name);
        if (fragment != null) {
            if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
            mCurTransaction.attach(fragment);// 因为fragment实例没有被真正释放,所以可以直接attach效率高。
        } else {
            fragment = getItem(position);// 初始化页面的时候拿到fragment的实例
            if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
            mCurTransaction.add(container.getId(), fragment,
                    makeFragmentName(container.getId(), itemId));
        }
        if (fragment != mCurrentPrimaryItem) {
            fragment.setMenuVisibility(false);
            fragment.setUserVisibleHint(false);
        }

        return fragment;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
                + " v=" + ((Fragment)object).getView());
        mCurTransaction.detach((Fragment)object);// 并没有真正释放fragment对象只是detach
    }

从源码中我们可以看出:FragmentStatePagerAdapter中fragment实例在destroyItem的时候被真正释放,FragmentPagerAdapter中的fragment实例在destroyItem的时候并没有真正释放fragment对象只是detach。所以FragmentStatePagerAdapter省内存,而FragmentPagerAdapter会消耗更多的内存,带来的好处就是效率更高一些。所以得出这样的结论:FragmentPagerAdapter适用于页面比较少的情况,FragmentStatePagerAdapter适用于页面比较多的情况,但是对性能有要求的情况下,推荐使用FragmentPagerAdapter。因此,“爱阅”为了突出性能,选择了FragmentPagerAdapter进行业务功能实现。

4、ViewPager的翻页动画

为ViewPager设置适配器后,就可以正常使用了,接下来我们为ViewPager增加翻页动画,ViewPager提供了PageTransformer接口用于实现翻页动画。

官方提供的PageTransformer实现例子
public class DepthPageTransformer implements ViewPager.PageTransformer {
    private static final float MIN_SCALE = 0.75f;

    public void transformPage(View view, float position) {
        Log.d("DepthPageTransformer", view.getTag() + " , " + position + "");
        int pageWidth = view.getWidth();

        if (position < -1) { // [-Infinity,-1)
            // This page is way off-screen to the left.
            view.setAlpha(0);

        } else if (position <= 0) { // [-1,0]
            // Use the default slide transition when moving to the left page
            view.setAlpha(1);
            view.setTranslationX(0);
            view.setScaleX(1);
            view.setScaleY(1);

        } else if (position <= 1) { // (0,1]
            // Fade the page out.
            view.setAlpha(1 - position);

            // Counteract the default slide transition
            view.setTranslationX(pageWidth * -position);

            // Scale the page down (between MIN_SCALE and 1)
            float scaleFactor = MIN_SCALE
                    + (1 - MIN_SCALE) * (1 - Math.abs(position));
            view.setScaleX(scaleFactor);
            view.setScaleY(scaleFactor);

        } else { // (1,+Infinity]
            // This page is way off-screen to the right.
            view.setAlpha(0);
        }
    }
}
public class ZoomOutPageTransformer implements ViewPager.PageTransformer {
    private static final float MIN_SCALE = 0.85f;
    private static final float MIN_ALPHA = 0.5f;

    @SuppressLint("NewApi")
    public void transformPage(View view, float position) {
        int pageWidth = view.getWidth();
        int pageHeight = view.getHeight();

        Log.e("TAG", view + " , " + position + "");

        if (position < -1) { // [-Infinity,-1)
            // This page is way off-screen to the left.
            view.setAlpha(0);

        } else if (position <= 1) 
        { // [-1,1]
            // Modify the default slide transition to shrink the page as well
            float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position));
            float vertMargin = pageHeight * (1 - scaleFactor) / 2;
            float horzMargin = pageWidth * (1 - scaleFactor) / 2;
            if (position < 0) {
                view.setTranslationX(horzMargin - vertMargin / 2);
            } else {
                view.setTranslationX(-horzMargin + vertMargin / 2);
            }

            // Scale the page down (between MIN_SCALE and 1)
            view.setScaleX(scaleFactor);
            view.setScaleY(scaleFactor);

            // Fade the page relative to its size.
            view.setAlpha(MIN_ALPHA + (scaleFactor - MIN_SCALE)
                    / (1 - MIN_SCALE) * (1 - MIN_ALPHA));

        } else { // (1,+Infinity]
            // This page is way off-screen to the right.
            view.setAlpha(0);
        }
    }
}

实现翻页动画的关键就是重写transformPage方法,方法里有两个参数view和position,理解这两个参数非常重要。假设有三个页面view1、view2、view3从左至右在viewPager中显示:往左滑动时view1、view2、view3的position都是不断变小的;往右滑动时view1、view2、view3的position都是不断变大的。当position是正负无穷大时view就离开屏幕视野了。因此最核心的控制逻辑是在[-1,0]和(0,1]这两个区间,通过设置透明度、平移、旋转、缩放等动画组合可以实现各式各样的页面变化效果。

5、ViewPager的高级用法

这里主要介绍的是ViewPager结合第三方库实现小圆点指示器效果和ViewPager结合design库实现tab切换,首先我们看下实现效果:

代码并不很复杂就不过多啰嗦了,大家可以到这里来获取源码:ViewPager指示器效果

6、ViewPager使用中遇到的坑

在“爱阅”的开发过程中我遇到了这样的现象:更新数据源之后视图并没有立即刷新,多滑动几次再次回到更新的Item时才更新。

对应刷新部分的代码编写是这样的:
    public void onCategorysChange(String key) {
        if (key != null) {
            mCategoryList = DataCache.getInstance().getChildCategorys(key);
            notifyDataSetChanged();
        } else {
            DLog.e("onCategorysChange() Error!");
        }
    }

并且不仅仅是更新数据,在单纯的添加和删除数据的时候同样会出现这样的问题。那么究竟是什么原因呢?我们对上文提到的三种适配器分别做出方案解答。

(1)PagerAdapter的解决方案

先来了解下 ViewPager 的刷新过程:
- 刷新的起始
ViewPager 的刷新是从调用其 PagerAdapter 的 notifyDataSetChanged() 方法开始的,那先看看该方法的源码:

public void notifyDataSetChanged() {
    synchronized (this) {
        if (mViewPagerObserver != null) {
            mViewPagerObserver.onChanged();
        }
    }
    mObservable.notifyChanged();
}
  • DataSetObservable 的 notifyChanged()
    上面的方法中出现了两个关键的成员变量:
private final DataSetObservable mObservable = new DataSetObservable();
private DataSetObserver mViewPagerObserver;

发现这是我们熟知的观察者模式,接下来看看 mObservable.notifyChanged() 做了些什么工作:

public void notifyChanged() {
    synchronized(mObservers) {
        for (int i = mObservers.size() - 1; i >= 0; i--) {
            mObservers.get(i).onChanged(); 
        }
    }
}

notifyChanged() 方法中是很典型的观察者模式中遍历所有的 Observer,通知变化发生了的代码,接下来看看这个 mObservers 包含哪些 Observer 。

  • DataSetObserver
    直接从 mObservers 点进去你会发现这个:
protected final ArrayList<T> mObservers = new ArrayList<T>();

public abstract class DataSetObserver {
    public void onChanged() {
        // Do nothing
    }
    public void onInvalidated() {
        // Do nothing
    }
}
  • PagerObserver 内部类
    PagerObserver 是 ViewPager 中的一个内部类,实现就是调用了 ViewPager 中的 dataSetChanged() 方法,真正的关键来了:
private class PagerObserver extends DataSetObserver {
    @Override
    public void onChanged() {
        dataSetChanged();
    }
    @Override
    public void onInvalidated() {
        dataSetChanged();
    }
}
  • ViewPager 的 dataSetChanged()
    这个方法的实现较长,里面的逻辑看上去挺复杂的,这里就不展示全部的源码了,列下关键点:
for (int i = 0; i < mItems.size(); i++) {
    final ItemInfo ii = mItems.get(i);
    final int newPos = mAdapter.getItemPosition(ii.object);

    if (newPos == PagerAdapter.POSITION_UNCHANGED) {
        continue;
    }

    if (newPos == PagerAdapter.POSITION_NONE) {
        ...
        continue;
    }
}

上面截取的代码中 for 循环里面有两个 continue 语句,这可能是比较关键的代码,幸好不用我们继续深入了,官方给出了解释:如果 Item 的位置如果没有发生变化,则返回 POSITION_UNCHANGED。如果返回了 POSITION_NONE,表示该位置的 Item 已经不存在了。默认的实现是假设 Item 的位置永远不会发生变化,而返回 POSITION_UNCHANGED。

说道这里,我们需要了解下 Viewpager 的刷新过程:

在每次调用 PagerAdapter 的 notifyDataSetChanged() 方法时,都会激活 getItemPosition(Object object) 方法,该方法会遍历 ViewPager 的所有 Item(由缓存的 Item 数量决定,默认为当前页和其左右加起来共3页,这个可以自行设定,但是至少会缓存2页),为每个 Item 返回一个状态值(POSITION_NONE/POSITION_UNCHANGED),如果是 POSITION_NONE,那么该 Item 会被 destroyItem(ViewGroup container, int position, Object object) 方法 remove 掉然后重新加载,如果是 POSITION_UNCHANGED就不会重新加载。默认是 POSITION_UNCHANGED,所以如果不重写 getItemPosition(Object object)并修改返回值,就无法看到 notifyDataSetChanged() 的刷新效果。

最简单的解决方案:

那就是直接重写PagerAdapter的getItemPosition(Object object)方法,将返回值固定为POSITION_NONE。正如前文所写的那样:

    @Override
    public int getItemPosition(Object object) {
        return POSITION_NONE;
    }
该方案的缺点:

有个很明显的缺陷,那就是会刷新所有的 Item,这将导致系统资源的浪费,所以这种方式不适合数据量较大的场景。

注意:

这种方式还有一个需要注意的地方,就是重写 destoryItem() 方法:

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    // 把 Object 强转为 View,然后将 view 从 ViewGroup 中清除
    container.removeView((View) object);
}

最简方案的优化

在 instantiateItem() 方法中给每个 View 添加 tag(使用 setTag() 方法),然后在 getItemPosition() 方法中通过 View.getTag() 来判断是否是需要刷新的页面,是就返回 POSITION_NONE,否就返回 POSITION_UNCHANGED。

注意:

这里有一点要注意的是,当清空数据源的时候需要返回 POSITION_NONE,可用如下代码:

if (mDataList != null && mDataList.size()==0) {
    return POSITION_NONE;
}

关于 PagerAdapter 的介绍就到这里了,虽然 FragmentPagerAdapter 与 FragmentStatePagerAdapter 都是继承自 PagerAdapter。但是这两个是专门为以 Fragment 为 Item 的 ViewPager 所准备的,所以有其特殊性,我们下面来介绍。

(2)FragmentPagerAdapter的解决方案

上面通过使 getItemPosition() 方法返回 POSITION_NONE 到达数据源变化(也就是调用 notifyDataSetChanged())时刷新视图的目的。但是当我们使用 Fragment 作为 ViewPager 的 Item 时,就需要多考虑一些了,而且一般是使用 FragmentPagerAdapter 或者 FragmentStatePagerAdapter。

解决方案:清除 FragmentManager 中缓存的 Fragment

当数据源发生变化时,先将 FragmentManger 里面所有缓存的 Fragment 全部清除,然后重新创建,这样达到刷新视图的目的。下面给出核心代码:

public class FPagerAdapter1 extends FragmentPagerAdapter {

    private ArrayList<Fragment> mFragmentList;
    private FragmentManager mFragmentManager;

    public FPagerAdapter1(FragmentManager fm, List<Integer> types) {
        super(fm);
        this.mFragmentManager = fm;
        mFragmentList = new ArrayList<>();
        for (int i = 0, size = types.size(); i < size; i++) {
            mFragmentList.add(FragmentTest.instance(i));
        }
        setFragments(mFragmentList);
    }

    public void updateData(List<Integer> dataList) {
        ArrayList<Fragment> fragments = new ArrayList<>();
        for (int i = 0, size = dataList.size(); i < size; i++) {
            Log.e("FPagerAdapter1", dataList.get(i).toString());
            fragments.add(FragmentTest.instance(dataList.get(i)));
        }
        setFragments(fragments);
    }

    private void setFragments(ArrayList<Fragment> mFragmentList) {
        if(this.mFragmentList != null){
            FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
            for(Fragment f:this.mFragmentList){
                fragmentTransaction.remove(f);
            }
            fragmentTransaction.commit();
            mFragmentManager.executePendingTransactions();
        }
        this.mFragmentList = mFragmentList;
        notifyDataSetChanged();
    }

    @Override
    public int getCount() {
        return this.mFragmentList.size();
    }

    public int getItemPosition(Object object) {
        return POSITION_NONE;
    }

    @Override
    public Fragment getItem(int position) {
        return mFragmentList.get(position);
    }
}

但是,这样做有一个缺点,那就是会造成不必要的浪费,会影响性能。还有就是必须使用一个 List 缓存所有的 Fragment,这也得占用不少内存,那接下来看看如何去优化。

优化方案:通过 Tag 获取缓存的 Fragment

首先我们应该知道 FragmentManager 是通过 Tag 找相应的 Fragment,从而达到缓存 Fragment 的目的。如果可以找到,就不会创建新的 Fragment,Fragment 的 onCreate()、onCreateView() 等方法都不会再次调用。那优化的思路就有了:

首先,需要缓存所有 Fragment 的 Tag:
private List<String> mTagList; // 用来存放所有的 Tag

// 生成 Tag:直接从 FragmentPageAdapter 源码里拷贝 Fragment 生成 Tag 的方法
private String makeFragmentName(int viewId, int index) {
    return "android:switcher:" + viewId + ":" + index;
}

// 将 Tag 缓存到 List 中
@Override
public Object instantiateItem(ViewGroup container, int position) {
    mTagList.add(position, makeFragmentName(container.getId(),
            (int) getItemId(position)));
    return super.instantiateItem(container, position);
}
其次,在更新 Fragment 时使用相应的 Tag 去 FragmentManamager 中找相应的 Fragment,如果存在就直接更新:
public void update(int position, String str) {
    Fragment fragment = mFragmentManager.findFragmentByTag(mTagList.get(position));
    if (fragment == null) return;
    if (fragment instanceof FragmentTest) {
        ((FragmentTest)fragment).update(str);
    }
    notifyDataSetChanged();
}

该方法需要自行在 Fragment 中提供。

最后,对于动态改变 ViewPager 中 Fragment 的数量,如果是添加那没什么要注意的,但是删除就有点棘手,这里给出实例代码:
public void remove(int position) {
    mDataList.remove(position);
    isDataSetChange = true;
    Fragment fragment = mFragmentManager.findFragmentByTag(mTagList.get(position));
    mTagList.remove(position);
    if (fragment == null) {
        notifyDataSetChanged();
        return;
    }
    FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
    fragmentTransaction.remove(fragment);
    fragmentTransaction.commit();
    mFragmentManager.executePendingTransactions();
    notifyDataSetChanged();
}

(3)“爱阅”中的解决方案

“爱阅”中的处理方式和上述优化的方式很像,但是又做了进一步的优化,代码如下所示:

    public void updateView(String pagerKey) {
        for (int i = 0; i < mViewPagerAdapter.getCount(); i++) {
            Fragment fragment = getSupportFragmentManager().findFragmentByTag(mViewPagerAdapter.getFragmentTag(R.id.viewpager_main, i));
            if (null != fragment) {
                String categoryKey = DataCache.getInstance().getCategory(pagerKey).getNextKey(i);
                if (categoryKey != null) {
                    Category viewPagerCategory = DataCache.getInstance().getCategory(categoryKey);
                    ViewPagerFragment viewPagerFragment = (ViewPagerFragment) fragment;
                    viewPagerFragment.setCategoryData(viewPagerCategory);
                }
            }
        }
    }
    public String getFragmentTag(int viewPagerId, int fragmentPosition) {
        return "android:switcher:" + viewPagerId + ":" + fragmentPosition;
    }

可以看到,我们通过getSupportFragmentManager().findFragmentByTag()方法去ViewPager中找到当前缓存的Fragment,经过非空判断后到数据源中获取要更新的数据,然后把对应的Fragment中的数据进行更新,以此来实现界面的更新。这样做的好处是:不用ViewPager进行操作,从而使数据的处理轻量级,不必因为刷新界面而消耗过多的CPU资源。

(4)FragmentStatePagerAdapter的解决方案

FragmentStatePagerAdapter 与 FragmentPagerAdapter 类似,这两个类都继承自 PagerAdapter。但是和 FragmentPagerAdapter 不一样的是,FragmentStatePagerAdapter 只保留当前页面,当页面离开视线后就会被消除并释放其资源;而在页面需要显示时,生成新的页面(这和 ListView 的实现一样)。这种方式的好处就是当拥有大量的页面时,不必在内存中占用大量的内存。

public class FSPagerAdapter extends FragmentStatePagerAdapter {

    private ArrayList<Fragment> mFragmentList;

    public FSPagerAdapter(FragmentManager fm, List<Integer> types) {
        super(fm);
        updateData(types);
    }

    public void updateData(List<Integer> dataList) {
        ArrayList<Fragment> fragments = new ArrayList<>();
        for (int i = 0, size = dataList.size(); i < size; i++) {
            Log.e("FPagerAdapter1", dataList.get(i).toString());
            fragments.add(FragmentTest.instance(dataList.get(i)));
        }
        setFragmentList(fragments);
    }

    private void setFragmentList(ArrayList<Fragment> fragmentList) {
        if(this.mFragmentList != null){
            mFragmentList.clear();
        }
        this.mFragmentList = fragmentList;
        notifyDataSetChanged();
    }

    @Override
    public int getCount() {
        return this.mFragmentList.size();
    }

    public int getItemPosition(Object object) {
        return POSITION_NONE;
    }

    @Override
    public Fragment getItem(int position) {
        return mFragmentList.get(position);
    }
}

上述代码是对该情况下的一种解决方式,对应的解释如下:

1、缓存所有的 Fragment

使用一个 List 将数据源对应的 Fragment 都缓存起来

2、更新数据源,刷新 Fragment

当有数据源更新的时候,从 List 中取出相应的 Fragment,然后刷新 Adapter

3、删除数据时,删除 List 中对应的 Fragment

当数据源中删除某项时,将 List 中对应的 Fragment 也删除,然后刷新 Adapter

总结

关于 ViewPager 的使用和 ViewPager 数据源刷新的问题到此我们就实现完毕了,其中 ViewPager 数据源刷新的问题比较麻烦的地方是从数据源中删除数据的情况,这和 ViewPager 的实现方式有关,我们在解决该问题的时候要分具体情况来采取不同的方案。

至此为止,我们今天一半的工作量就已经完成了,接下来的时间我们一起去结合 TabLayout 实现顶部的导航功能,我们下篇见!

联系方式:

简书:WillFlow
CSDN:WillFlow
微信公众号:WillFlow

微信公众号:WillFlow

上一篇:Day2 当前最火爆的APP架构及其实现 下一篇:Day7 Glide的三级数据结构缓存设计