Viewpager+FragmentStatePagerAdapter动态添加,删除,移动位置,(局部)更新页面(Fragment)不闪屏,不错位解决方案

首先当然是引出要解决的需求,在做一个IM模块时UI是这样的:上面是水平的联系人栏,下面是聊天界面,可以水平滑动切换联系人聊天,也就是说是RecyclerView与Viewpager的联动,为了切换时体验好需要左右两边预加载多个联系人的聊天内容,用户收到新消息时当前聊天页变成第一页(Viewpager页面要无闪烁或滑动)顶栏头像移动到第一个,删除聊天时移除当前聊天页其他缓存的页不变动,不闪烁,也就是说页面位置发生变化时已有缓存页的要用缓存而不是销毁重建,大体UI请看图:

image

简书上有朋友让我写个demo出来给他,这不今天抽时间写了

openpageradapter.gif

1. 要实现的几个功能点:

  • ViewPager中Fragment多少个不固定,需要动态添加,删除页面。
  • 更新页面(Fragment),使用已有的缓存页面。
  • 移动页面(Fragment)位置,使用已预加载的缓存页面。

问题:

  1. 调用notifyDataSetChanged并没有去更新内容。
  2. 添加,删除,移动页面后会错位,position与Fragment对不上。

2. 误区

在网上搜索会发现,更新ViewPager的方法基本都是说在apdater的getItemPosition()方法里返回POSITION_NONE,这确实会更新,但也会导致所有页面被销毁重建,会出现闪烁等问题,性能上也会不太好。

3. 原理

要做到动态的更新,添加,删除ViewPager的数据,我们需要先弄清ViewPager+adapter是怎么管理页面的,首先我假设看此文的你已经熟悉了PagerAdapter的各方法作用,如果没有的话还请先去查一下,这里以FragmentStatePagerAdapter为例,当调用adapter的notifyDataSetChanged后会通知到viewpager检查更新数据:

void 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) {
              mItems.remove(i);
              i--;

              if (!isUpdating) {
                  mAdapter.startUpdate(this);
                  isUpdating = true;
              }

              mAdapter.destroyItem(this, ii.position, ii.object);
              needPopulate = true;

              if (mCurItem == ii.position) {
                  // Keep the current item in the valid range
                  newCurrItem = Math.max(0, Math.min(mCurItem, adapterCount - 1));
                  needPopulate = true;
              }
              continue;
          }

          if (ii.position != newPos) {
              if (ii.position == mCurItem) {
                  // Our current item changed position. Follow it.
                  newCurrItem = newPos;
              }

              ii.position = newPos;
              needPopulate = true;
          }
      }
      ...
      Collections.sort(mItems, COMPARATOR);
  ...
}
...
    static class ItemInfo {
      Object object;
      int position;
      boolean scrolling;
      float widthFactor;
      float offset;
  }

上面是ViewPager dataSetChanged方法里的一段代码,可以看到ViewPager会遍历它缓存的Item集合,询问(mAdapter.getItemPosition)Adapter每一个Item是删除了(POSITION_NONE),还是更新了位置(ii.position != newPos),还是没变化(POSITION_UNCHANGED),然后对mItems集合根据新赋值的position进行重新排序。
接下来根据我们返回的答案进行操作,如果是没变则不做更改,也就是不会更新内容(PagerAdapter中的getItemPosition默认是直接返回POSITION_NONE,所以调用notifyDataSetChanged默认情况下是不会更新的),如果是删除了则从mItems集合中删除并让adapter也删除( mAdapter.destroyItem),如果是更新了位置则根据位置的变化对页面进行更新,会重新布局(requestLayout)。而在ViewPaer的onLayout方法里会遍历每个子view然后调用infoForChild()方法从mItems里找到每个子view对应的itemInfo:

    ItemInfo infoForChild(View child) {
      for (int i = 0; i < mItems.size(); i++) {
          ItemInfo ii = mItems.get(i);
          if (mAdapter.isViewFromObject(child, ii.object)) {
              return ii;
          }
      }
      return null;
  }

看上面代码我们知道是通过mAdapter.isViewFromObject()来判断某个ViewPager的子view到底对应的是哪个Fragment的rootView的,所以我们一般在adapter的isViewFromObject方法中会这么写:

    public boolean isViewFromObject(View view, Object object) {
      return ((Fragment)object).getView() == view;
  }

这样就把第几页要显示哪个fragment的view对应上了,在onlayout时通过子view位置与itemInfo中的offset(相当于偏移到第几页)完成了按顺序排列页面。

从上面的分析我们知道了adapter的getItemPosition()其实我们不仅仅只可以返回POSITION_NONE与POSITION_UNCHANGED还可以根据我们的需要返回一个更新adapter数据后的新位置

总结一下,ViewPager更新过程分为这几步:

  1. adapter调用notifyDataSetChanged,ViewPager开始检测更新.
  2. 通过adapter相应的方法询问出ViewPager中缓存的页面在新数据中的更改情况。
  3. 有更新的话重新调整布局中View的位置。

4. 处理问题1

弄清理了原理,那么解决问题1了:我们需要在Viewpager询问时告诉ViewPagerItem有哪些变化:

  1. 缓存中某个位置的数据是否与新数据一样
  2. 缓存老数据在新数据中的位置

处理方式参考了此文:FragmentPagerAdapter 和 FragmentStatePagerAdapter 的数据更新问题

public abstract class FixedPagerAdapter<T> extends FragmentStatePagerAdapter {

    private List<ItemObject> mCurrentItems = new ArrayList<>();

    public FixedPagerAdapter(FragmentManager fragmentManager) {
        super(fragmentManager);
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        while (mCurrentItems.size() <= position) {
            mCurrentItems.add(null);
        }
        Fragment fragment = (Fragment) super.instantiateItem(container, position);
        ItemObject object = new ItemObject(fragment, getItemData(position));
        mCurrentItems.set(position, object);
        return object;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        mCurrentItems.set(position, null);
        super.destroyItem(container, position, ((ItemObject) object).fragment);
    }

    @Override
    public int getItemPosition(Object object) {
        ItemObject itemObject = (ItemObject) object;
        if (mCurrentItems.contains(itemObject)) {
            T oldData = itemObject.t;
            int oldPosition = mCurrentItems.indexOf(itemObject);
            T newData = getItemData(oldPosition);
            if (equals(oldData, newData)) {
                return POSITION_UNCHANGED;
            } else {
                int newPosition = getDataPosition(oldData);
                return newPosition >= 0 ? newPosition : POSITION_NONE;
            }
        }
        return POSITION_UNCHANGED;
    }

    @Override
    public void setPrimaryItem(ViewGroup container, int position, Object object) {
        super.setPrimaryItem(container, position, ((ItemObject) object).fragment);
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return super.isViewFromObject(view, ((ItemObject) object).fragment);
    }

    public abstract T getItemData(int position);

    public abstract int getDataPosition(T t);

    public abstract boolean equals(T oldD, T newD);

    public class ItemObject {

        public Fragment fragment;
        public T t;

        public ItemObject(Fragment fragment, T t) {
            this.fragment = fragment;
            this.t = t;
        }
    }

}

在Adapter中对当前ViewPger中缓存的页面数据进行了保存,然后通过三个抽象方法进行对比:getItemData() ,getDataPosition(),equals(),使用时只需要新建一个Adapter类继承FixedPagerAdapter完成抽象方法的实现。

这样做后更新确实没问题了,比较科学,不会闪烁,全部销毁,但别忘了我们还要动态的添加,删除,移动位置,这时如果只是这么处理就会导致页面错位问题

原因就是当你添加,删除,移动页面时ViewPager是通过getItemPosition方法对它的缓存集合进行了对应处理,但我们的FixedPagerAdapter中的缓存并没有做删除,增加位置,排序。

5. 处理问题2

  1. 对FragmentStatePagerAdapter的源码进行修改,建立一个OpenFragmentStatePagerAdapter类,把原来缓存的Fragment List变成缓存我们自己的ItemInfo,ItemInfo中保存了3个数据 :fragment,数据和页面所处的位置。
  2. 在instantiateItem ,destroyItem 时对List中的ItemInfo进行增加,删除,在getItemPosition时对list中ItemInfo的position按新数据的位置进行赋值。
  3. 在notifyDataSetChanged后和instantiateItem获取缓存的ItemInfo发现位置不对时进行缓存list的排序,增加等调整。

具体请看代码,重要位置写了注释:

/**
* Created by homgwu on 2018/4/2 14:29.
*/
public abstract class OpenFragmentStatePagerAdapter<T> extends PagerAdapter {
  private static final String TAG = "FragmentStatePagerAdapt";
  private static final boolean DEBUG = false;

  private final FragmentManager mFragmentManager;
  private FragmentTransaction mCurTransaction = null;

  private ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();
  private ArrayList<ItemInfo<T>> mItemInfos = new ArrayList();
  private Fragment mCurrentPrimaryItem = null;
  private boolean mNeedProcessCache = false;

  public OpenFragmentStatePagerAdapter(FragmentManager fm) {
      mFragmentManager = fm;
  }

  /**
   * Return the Fragment associated with a specified position.
   */
  public abstract Fragment getItem(int position);

  protected Fragment getCachedItem(int position) {
      return mItemInfos.size() > position ? mItemInfos.get(position).fragment : null;
  }

  @Override
  public void startUpdate(ViewGroup container) {
      if (container.getId() == View.NO_ID) {
          throw new IllegalStateException("ViewPager with adapter " + this
                  + " requires a view id");
      }
  }

  @Override
  public Object instantiateItem(ViewGroup container, int position) {
      // If we already have this item instantiated, there is nothing
      // to do.  This can happen when we are restoring the entire pager
      // from its saved state, where the fragment manager has already
      // taken care of restoring the fragments we previously had instantiated.
      if (mItemInfos.size() > position) {
          ItemInfo ii = mItemInfos.get(position);
          if (ii != null) {
        //判断位置是否相等,如果不相等说明新数据有增加或删除(导致了ViewPager那边有空位),
              // 而这时notifyDataSetChanged方法还没有完成,ViewPager会先调用instantiateItem来获取新的页面
              //所以为了不取错页面,我们需要对缓存进行检查和调整位置:checkProcessCacheChanged
              if (ii.position == position) {
                  return ii;
              } else {
                  checkProcessCacheChanged();
              }
          }
      }

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

      Fragment fragment = getItem(position);
      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 (mItemInfos.size() <= position) {
          mItemInfos.add(null);
      }
      fragment.setMenuVisibility(false);
      fragment.setUserVisibleHint(false);
      ItemInfo<T> iiNew = new ItemInfo<>(fragment, getItemData(position), position);
      mItemInfos.set(position, iiNew);
      mCurTransaction.add(container.getId(), fragment);

      return iiNew;
  }

  @Override
  public void destroyItem(ViewGroup container, int position, Object object) {
      ItemInfo ii = (ItemInfo) 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, ii.fragment.isAdded()
              ? mFragmentManager.saveFragmentInstanceState(ii.fragment) : null);
      mItemInfos.set(position, null);

      mCurTransaction.remove(ii.fragment);
  }

  @Override
  @SuppressWarnings("ReferenceEquality")
  public void setPrimaryItem(ViewGroup container, int position, Object object) {
      ItemInfo ii = (ItemInfo) object;
      Fragment fragment = ii.fragment;
      if (fragment != mCurrentPrimaryItem) {
          if (mCurrentPrimaryItem != null) {
              mCurrentPrimaryItem.setMenuVisibility(false);
              mCurrentPrimaryItem.setUserVisibleHint(false);
          }
          if (fragment != null) {
              fragment.setMenuVisibility(true);
              fragment.setUserVisibleHint(true);
          }
          mCurrentPrimaryItem = fragment;
      }
  }

  @Override
  public void finishUpdate(ViewGroup container) {
      if (mCurTransaction != null) {
          mCurTransaction.commitNowAllowingStateLoss();
          mCurTransaction = null;
      }
  }

  @Override
  public boolean isViewFromObject(View view, Object object) {
      Fragment fragment = ((ItemInfo) object).fragment;
      return fragment.getView() == view;
  }

  @Override
  public int getItemPosition(Object object) {
      mNeedProcessCache = true;
      ItemInfo<T> itemInfo = (ItemInfo) object;
      int oldPosition = mItemInfos.indexOf(itemInfo);
      if (oldPosition >= 0) {
          T oldData = itemInfo.data;
          T newData = getItemData(oldPosition);
          if (dataEquals(oldData, newData)) {
              return POSITION_UNCHANGED;
          } else {
              ItemInfo<T> oldItemInfo = mItemInfos.get(oldPosition);
              int oldDataNewPosition = getDataPosition(oldData);
              if (oldDataNewPosition < 0) {
                  oldDataNewPosition = POSITION_NONE;
              }
              //把新的位置赋值到缓存的itemInfo中,以便调整时使用
              if (oldItemInfo != null) {
                  oldItemInfo.position = oldDataNewPosition;
              }
              return oldDataNewPosition;
          }

      }

      return POSITION_UNCHANGED;
  }

  @Override
  public void notifyDataSetChanged() {
      super.notifyDataSetChanged();
      //通知ViewPager更新完成后对缓存的ItemInfo List进行调整
      checkProcessCacheChanged();
  }

  private void checkProcessCacheChanged() {
      //只有调用过getItemPosition(也就是有notifyDataSetChanged)才进行缓存的调整
      if (!mNeedProcessCache) return;
      mNeedProcessCache = false;
      ArrayList<ItemInfo<T>> pendingItemInfos = new ArrayList<>(mItemInfos.size());
      //先存入空数据
      for (int i = 0; i < mItemInfos.size(); i++) {
          pendingItemInfos.add(null);
      }
      //根据缓存的itemInfo中的新position把itemInfo入正确的位置
      for (ItemInfo<T> itemInfo : mItemInfos) {
          if (itemInfo != null) {
              if (itemInfo.position >= 0) {
                  while (pendingItemInfos.size() <= itemInfo.position) {
                      pendingItemInfos.add(null);
                  }
                  pendingItemInfos.set(itemInfo.position, itemInfo);
              }
          }
      }
      mItemInfos = pendingItemInfos;
  }

  @Override
  public Parcelable saveState() {
      Bundle state = null;
      if (mSavedState.size() > 0) {
          state = new Bundle();
          Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()];
          mSavedState.toArray(fss);
          state.putParcelableArray("states", fss);
      }
      for (int i = 0; i < mItemInfos.size(); i++) {
          Fragment f = mItemInfos.get(i).fragment;
          if (f != null && f.isAdded()) {
              if (state == null) {
                  state = new Bundle();
              }
              String key = "f" + i;
              mFragmentManager.putFragment(state, key, f);
          }
      }
      return state;
  }

  @Override
  public void restoreState(Parcelable state, ClassLoader loader) {
      if (state != null) {
          Bundle bundle = (Bundle) state;
          bundle.setClassLoader(loader);
          Parcelable[] fss = bundle.getParcelableArray("states");
          mSavedState.clear();
          mItemInfos.clear();
          if (fss != null) {
              for (int i = 0; i < fss.length; i++) {
                  mSavedState.add((Fragment.SavedState) fss[i]);
              }
          }
          Iterable<String> keys = bundle.keySet();
          for (String key : keys) {
              if (key.startsWith("f")) {
                  int index = Integer.parseInt(key.substring(1));
                  Fragment f = mFragmentManager.getFragment(bundle, key);
                  if (f != null) {
                      while (mItemInfos.size() <= index) {
                          mItemInfos.add(null);
                      }
                      f.setMenuVisibility(false);
                      ItemInfo<T> iiNew = new ItemInfo<>(f, getItemData(index), index);
                      mItemInfos.set(index, iiNew);
                  } else {
                      Log.w(TAG, "Bad fragment at key " + key);
                  }
              }
          }
      }
  }

  protected Fragment getCurrentPrimaryItem() {
      return mCurrentPrimaryItem;
  }

  protected Fragment getFragmentByPosition(int position) {
      if (position < 0 || position >= mItemInfos.size()) return null;
      return mItemInfos.get(position).fragment;
  }

  abstract T getItemData(int position);

  abstract boolean dataEquals(T oldData, T newData);

  abstract int getDataPosition(T data);

  static class ItemInfo<D> {
      Fragment fragment;
      D data;
      int position;

      public ItemInfo(Fragment fragment, D data, int position) {
          this.fragment = fragment;
          this.data = data;
          this.position = position;
      }
  }
}

这个是抽象的基类,然后我们在具体业务代码处写一个Adapter继承OpenFragmentStatePagerAdapter,就可以用极少的代码来使用了,当需要更新某个具体的页面时不需要notifyDataSetChanged,只需要用getFragmentByPosition来取出具体页面,然后局部更新内容

使用(因为项目中是kotlin写的,后面有朋友要demo时写了个java版在文章最后的github地址中):

/**
* Created by homgwu on 2018/3/23 10:12.
*/
class ListChatViewPagerAdapter(fragmentManager: FragmentManager, val viewPager: ViewPager) : OpenFragmentStatePagerAdapter<SessionItem>(fragmentManager), AnkoLogger {
   private var mData: ArrayList<SessionItem> = ArrayList()

   constructor(fragmentManager: FragmentManager, viewPager: ViewPager, data: List<SessionItem>?) : this(fragmentManager, viewPager) {
       mData.clear()
       if (data != null) mData.addAll(data)
   }

   override fun getItem(position: Int): Fragment {
       info {
           "getItem position=$position"
       }
       return ChatListFragment.newInstance(mData[position].data, ChatListFragment.COME_FROM_LIST_CHAT)
   }

   override fun getCount(): Int {
       info {
           "getCount count=${mData.size}"
       }
       return mData.size
   }

//    override fun getItemPosition(`object`: Any?): Int {
////        return findItemPosition(`object` as ChatListFragment)
//        return POSITION_NONE
//    }

   fun getCurrentFragmentItem(): ChatListFragment? {
       return getCurrentPrimaryItem() as? ChatListFragment
   }

   fun setNewData(data: List<SessionItem>) {
       mData.clear()
       mData.addAll(data)
       notifyDataSetChanged()
   }

   fun addData(sessionItem: SessionItem) {
       mData.add(sessionItem)
       notifyDataSetChanged()
   }

   fun addData(position: Int, sessionItem: SessionItem) {
       mData.add(position, sessionItem)
       notifyDataSetChanged()
   }

   fun remove(position: Int) {
       mData.removeAt(position)
       notifyDataSetChanged()
   }

   fun moveData(from: Int, to: Int) {
       if (from == to) return
       Collections.swap(mData, from, to)
//        updateByPosition(from, mData[from])
//        updateByPosition(to, mData[to])
       notifyDataSetChanged()
   }

   fun moveDataToFirst(from: Int) {
       val tempData = mData.removeAt(from)
       mData.add(0, tempData)
       notifyDataSetChanged()
   }

   fun updateByPosition(position: Int, sessionItem: SessionItem) {
       if (position >= 0 && mData.size > position) {
           mData[position] = sessionItem
           var targetF = getCachedFragmentByPosition(position)
           if (targetF != null) {
               targetF.resetData(sessionItem.data)
           }
       }
   }

   override fun getItemData(position: Int): SessionItem? {
       return if (mData.size > position) mData[position] else null
   }

   override fun dataEquals(oldData: SessionItem?, newData: SessionItem?): Boolean {
       return oldData == newData
   }

   override fun getDataPosition(data: SessionItem?): Int {
       return if (data == null) -1 else mData.indexOf(data)
   }

   fun getCachedFragmentByPosition(position: Int): ChatListFragment? {
       return getFragmentByPosition(position) as? ChatListFragment
   }

}

Kotlin版OpenFragmentStatePagerAdapter:

/**
* Created by homgwu on 2018/3/23 09:35.
*/
abstract class OpenFragmentStatePagerAdapter<T>(private val mFragmentManager: FragmentManager) : PagerAdapter() {

   private val TAG = "FragmentStatePagerAdapt"
   private val DEBUG = false

   private var mCurTransaction: FragmentTransaction? = null

   private val mSavedState = ArrayList<Fragment.SavedState?>()
   private var mItemInfos = ArrayList<ItemInfo<T>?>()
   protected var mCurrentPrimaryItem: Fragment? = null
   private var mNeedProcessCache = false

   /**
    * Return the Fragment associated with a specified position.
    */
   abstract fun getItem(position: Int): Fragment

   protected fun getCachedItem(position: Int): Fragment? = if (mItemInfos.size > position) mItemInfos[position]?.fragment else null

   override fun startUpdate(container: ViewGroup) {
       if (container.id == View.NO_ID) {
           throw IllegalStateException("ViewPager with adapter " + this
                   + " requires a view id")
       }
   }

   override fun instantiateItem(container: ViewGroup, position: Int): Any {
       // If we already have this item instantiated, there is nothing
       // to do.  This can happen when we are restoring the entire pager
       // from its saved state, where the fragment manager has already
       // taken care of restoring the fragments we previously had instantiated.
       if (mItemInfos.size > position) {
           val ii = mItemInfos[position]
           ii?.let {
               if (it.position == position) {
                   return this
               } else {
                   checkProcessCacheChanged()
               }
           }
       }

       val fragment = getItem(position)
       if (DEBUG) Log.v(TAG, "Adding item #$position: f=$fragment")
       if (mSavedState.size > position) {
           val fss = mSavedState[position]
           if (fss != null) {
               fragment.setInitialSavedState(fss)
           }
       }
       while (mItemInfos.size <= position) {
           mItemInfos.add(null)
       }
       fragment.setMenuVisibility(false)
       fragment.userVisibleHint = false
       val iiNew = ItemInfo(fragment, getItemData(position), position)
       mItemInfos[position] = iiNew
       if (mCurTransaction == null) {
           mCurTransaction = mFragmentManager.beginTransaction()
       }
       mCurTransaction!!.add(container.id, fragment)

       return iiNew
   }

   override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
       val ii = `object` as ItemInfo<T>

       if (DEBUG)
           Log.v(TAG, "Removing item #" + position + ": f=" + `object`
                   + " v=" + ii.fragment.view)
       while (mSavedState.size <= position) {
           mSavedState.add(null)
       }
       mSavedState[position] = if (ii.fragment.isAdded)
           mFragmentManager.saveFragmentInstanceState(ii.fragment)
       else
           null
       mItemInfos[position] = null
       if (mCurTransaction == null) {
           mCurTransaction = mFragmentManager.beginTransaction()
       }
       mCurTransaction!!.remove(ii.fragment)
   }

   override fun setPrimaryItem(container: ViewGroup, position: Int, `object`: Any?) {
       val ii = `object` as? ItemInfo<T>
       val fragment = ii?.fragment
       if (fragment != mCurrentPrimaryItem) {
           mCurrentPrimaryItem?.apply {
               setMenuVisibility(false)
               userVisibleHint = false
           }
           fragment?.apply {
               setMenuVisibility(true)
               userVisibleHint = true
           }
           mCurrentPrimaryItem = fragment
       }
   }

   override fun finishUpdate(container: ViewGroup) {
       mCurTransaction?.apply {
           commitNowAllowingStateLoss()
       }
       mCurTransaction = null
   }

   override fun isViewFromObject(view: View, `object`: Any): Boolean {
       val fragment = (`object` as ItemInfo<T>).fragment
       return fragment.view === view
   }

   override fun getItemPosition(`object`: Any?): Int {
       mNeedProcessCache = true
       val itemInfo: ItemInfo<T> = `object` as ItemInfo<T>
       val oldPosition = mItemInfos.indexOf(itemInfo)
       if (oldPosition >= 0) {
           val oldData: T? = itemInfo.data
           val newData: T? = getItemData(oldPosition)
           return if (dataEquals(oldData, newData)) {
               PagerAdapter.POSITION_UNCHANGED
           } else {
               val oldItemInfo = mItemInfos[oldPosition]
               var oldDataNewPosition = getDataPosition(oldData)
               if (oldDataNewPosition < 0) {
                   oldDataNewPosition = PagerAdapter.POSITION_NONE
               }
               oldItemInfo?.apply {
                   position = oldDataNewPosition
               }
               oldDataNewPosition
           }
       }
       return PagerAdapter.POSITION_UNCHANGED
   }

   override fun notifyDataSetChanged() {
       super.notifyDataSetChanged()
       checkProcessCacheChanged()
   }

   private fun checkProcessCacheChanged() {
       if (!mNeedProcessCache) return
       mNeedProcessCache = false
       val pendingItemInfos = ArrayList<ItemInfo<T>?>(mItemInfos.size)
       for (i in 0..(mItemInfos.size - 1)) {
           pendingItemInfos.add(null)
       }
       for (value in mItemInfos) {
           value?.apply {
               if (position >= 0) {
                   while (pendingItemInfos.size <= position) {
                       pendingItemInfos.add(null)
                   }
                   pendingItemInfos[value.position] = value
               }
           }
       }
       mItemInfos = pendingItemInfos
   }

   override fun saveState(): Parcelable? {
       var state: Bundle? = null
       if (mSavedState.size > 0) {
           state = Bundle()
           val fss = arrayOfNulls<Fragment.SavedState>(mSavedState.size)
           mSavedState.toArray(fss)
           state.putParcelableArray("states", fss)
       }
       for (i in mItemInfos.indices) {
           val f = mItemInfos[i]?.fragment
           if (f != null && f.isAdded) {
               if (state == null) {
                   state = Bundle()
               }
               val key = "f$i"
               mFragmentManager.putFragment(state, key, f)
           }
       }
       return state
   }

   override fun restoreState(state: Parcelable?, loader: ClassLoader?) {
       if (state != null) {
           val bundle = state as Bundle?
           bundle!!.classLoader = loader
           val fss = bundle.getParcelableArray("states")
           mSavedState.clear()
           mItemInfos.clear()
           if (fss != null) {
               for (i in fss.indices) {
                   mSavedState.add(fss[i] as Fragment.SavedState)
               }
           }
           val keys = bundle.keySet()
           for (key in keys) {
               if (key.startsWith("f")) {
                   val index = Integer.parseInt(key.substring(1))
                   val f = mFragmentManager.getFragment(bundle, key)
                   if (f != null) {
                       while (mItemInfos.size <= index) {
                           mItemInfos.add(null)
                       }
                       f.setMenuVisibility(false)
                       val iiNew = ItemInfo(f, getItemData(index), index)
                       mItemInfos[index] = iiNew
                   } else {
                       Log.w(TAG, "Bad fragment at key $key")
                   }
               }
           }
       }
   }

   protected fun getCurrentPrimaryItem() = mCurrentPrimaryItem
   protected fun getFragmentByPosition(position: Int): Fragment? {
       if (position < 0 || position >= mItemInfos.size) return null
       return mItemInfos[position]?.fragment
   }

   abstract fun getItemData(position: Int): T?

   abstract fun dataEquals(oldData: T?, newData: T?): Boolean

   abstract fun getDataPosition(data: T?): Int

   class ItemInfo<D>(var fragment: Fragment, var data: D?, var position: Int)
}

到此,我们已经可以愉快的使用ViewPager+OpenFragmentStatePagerAdapter来动态添加,删除,移动位置,更新或局部更新页面了。源码及Demo 在下面的github地址中:

作者:竹尘居士
GitHub:https://github.com/homgwu/OpenPagerAdapter
博客:http://zhuchenju.com
公众号:竹尘居 (zhuchenju92)

竹尘居

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容