The first, of course, is to introduce the requirements to be addressed. When making an IM module, the UI looks like this: Above is the horizontal contact bar, below is the chat interface, can slide horizontal switch contact chat, that is, RecyclerView and Viewpager linkage, in order to switch the experience of good need to pre-load the chat content of multiple contacts on both sides, Chat users receive a new message when the current page into the first page (Viewpager to flicker-free page or slide) top head moves to the first column, delete chat chat when removing the current other cached pages does not change, not flashing, which means the page position change existing cache pages to use the cache rather than the destruction of reconstruction, general UI see figure:

One of Jane’s book friends asked me to write a demo for him, so I got around to it today:

1. Several function points to be implemented:

  • The number of fragments in the ViewPager varies. You need to dynamically add and delete pages.
  • Update the page (Fragment) to use the existing cached page.
  • Fragment the location of a page, using a preloaded cached page.

Question:

  1. Calling notifyDataSetChanged does not update the content.
  2. Add, delete, move the page dislocation, position and Fragment does not match.

2. The erroneous zone

A web search will show that the most common way to update a ViewPager is to return POSITION_NONE in Apdater’s getItemPosition() method. This does update, but it also causes all pages to be destroyed and rebuilt, causing flickering and poor performance.

Principle 3.

To dynamically update, add, and delete ViewPager data, we need to first understand how the ViewPager+ Adapter management page, first I assume that you are familiar with the PagerAdapter methods, if not, please check first. Here in FragmentStatePagerAdapter, for example, when calling adapter notifyDataSetChanged will notify to the viewpager check update the data:

void dataSetChanged(a) {...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;
  }
Copy the code

Above is the code in the ViewPager dataSetChanged method. You can see that the ViewPager iterates through its cached Item collection. Ask (madapter.getitemPosition)Adapter whether each Item is deleted (POSITION_NONE) or updated (ii.position! = newPos), remained unchanged (POSITION_UNCHANGED), and then reordered the mItems collection according to the newly assigned position. The PagerAdapter getItemPosition returns POSITION_NONE by default. So calling notifyDataSetChanged is not updated by default), if it is deleted, remove it from the mItems collection and let adapter delete it (mAdapt.DestroyItem), If the location is updated, the page will be updated based on the location change, and the requestLayout will be renewed. The onLayout method of ViewPaer iterates through each child view and calls infoForChild() to find the itemInfo for each child view from mItems:

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

See the code above we know is through mAdapter isViewFromObject () to determine a ViewPager child view which is exactly the corresponding rootView fragments, So what we usually do in the isViewFromObject method of adapter is we say:

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

In onLayout, the child view position is offset from itemInfo (which page is offset to itemInfo) to complete the order of the page.

The adapter getItemPosition() doesn’t just return POSITION_NONE or POSITION_UNCHANGED, it also returns a new POSITION_UNCHANGED position as needed

To summarize, the ViewPager update process is broken down into these steps:

  1. The Adapter calls notifyDataSetChanged, and the ViewPager starts detecting updates.
  2. Query the page cached in the ViewPager for changes in the new data through the Adapter’s corresponding methods.
  3. Reposition the View in the layout if there is an update.

4. Deal with Problem 1

Clearing up the principle solves problem 1: We need to tell ViewPagerItem what has changed when the Viewpager asks:

  1. Whether the data at a location in the cache is the same as the new data
  2. Caches the location of old data in new data

Way to refer to the article: FragmentPagerAdapter and FragmentStatePagerAdapter data update problem

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; }}}Copy the code

The page data cached in the current ViewPger is saved in Adapter and compared using three abstract methods: GetItemData (), getDataPosition(),equals(), just need to create a new Adapter class inherit FixedPagerAdapter to complete the abstract method implementation.

After doing this update is really no problem, more scientific, no flicker, all destroyed, but don’t forget that we also need to dynamically add, delete, move position, then if you just do this will lead to the page dislocation problem

The reason is that when you add, remove, or move pages the ViewPager does something to its cache collection using the getItemPosition method, but the cache in our FixedPagerAdapter doesn’t do anything to delete, add positions, sort.

5. Deal with Question 2

  1. Modify FragmentStatePagerAdapter source code and build a OpenFragmentStatePagerAdapter class, the cache fragments of the original List into our own cache ItemInfo, ItemInfo holds 3 pieces of data: fragment, data, and page location.
  2. Add and delete ItemInfo in List when instantiateItem, destroyItem, and assign position of ItemInfo in List according to position of new data when getItemPosition.
  3. After notifyDataSetChanged and instantiateItem get cached ItemInfo and find it not in the right position, sort the cached list, add it, etc.

Please refer to the code for details and write comments in important places:

/**
* 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) {
        // Check whether the positions are equal. If they are not equal, new data is added or deleted (resulting in empty space on the ViewPager side).
              // While the notifyDataSetChanged method is not complete, the ViewPager calls instantiateItem to retrieve the new page
              // So in order not to get the wrong page, we need to check the cache and adjust location: 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;
              }
              // Assign the new location to the cached itemInfo for adjustment
              if(oldItemInfo ! =null) {
                  oldItemInfo.position = oldDataNewPosition;
              }
              returnoldDataNewPosition; }}return POSITION_UNCHANGED;
  }

  @Override
  public void notifyDataSetChanged(a) {
      super.notifyDataSetChanged();
      // Notify ViewPager to adjust the cached ItemInfo List after the update is complete
      checkProcessCacheChanged();
  }

  private void checkProcessCacheChanged(a) {
      // Cache adjustments are made only after getItemPosition is called (notifyDataSetChanged)
      if(! mNeedProcessCache)return;
      mNeedProcessCache = false;
      ArrayList<ItemInfo<T>> pendingItemInfos = new ArrayList<>(mItemInfos.size());
      // Store null data first
      for (int i = 0; i < mItemInfos.size(); i++) {
          pendingItemInfos.add(null);
      }
      // Put the itemInfo in the correct position according to the new position in the cached 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(a) {
      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(a) {
      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; }}}Copy the code

This is an abstract base class, and then in the specific business code we write an Adapter inherit OpenFragmentStatePagerAdapter, can use very little code to use, when you need to update a particular page doesn’t need notifyDataSetChanged, Just use getFragmentByPosition to pull out the specific page and update the content locally

Use (because the project is written by Kotlin, after a friend to demo wrote a Java version in the end of the article github address) :

/**
* 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(a): 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(a): 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
   }

}
Copy the code

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 valmSavedState = ArrayList<Fragment.SavedState? > ()private varmItemInfos = 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]? .fragmentelse 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) {
           valii = 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>
       valfragment = ii? .fragmentif(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(a) {
       super.notifyDataSetChanged()
       checkProcessCacheChanged()
   }

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

   override fun saveState(a): 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) {
           valf = mItemInfos[i]? .fragmentif(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 asBundle? bundle!! .classLoader = loaderval 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(a) = mCurrentPrimaryItem
   protected fun getFragmentByPosition(position: Int): Fragment? {
       if (position < 0 || position >= mItemInfos.size) return null
       returnmItemInfos[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)}Copy the code

To this, we have can use ViewPager + OpenFragmentStatePagerAdapter pleasant to dynamically add, delete, move, update, or partial update the page. The source code and Demo are at github address below.

Author: bamboo dust lay GitHub:https://github.com/homgwu/OpenPagerAdapter blog: http://zhuchen.vip/2018/04/04/android/android-viewpager-adapter-update-move-add.html