美文网首页Android开发各种viewandroid
Viewpager+FragmentStatePagerAdap

Viewpager+FragmentStatePagerAdap

作者: 竹尘居士 | 来源:发表于2018-04-08 20:28 被阅读396次

    首先当然是引出要解决的需求,在做一个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://zhuchen.vip/2018/04/04/android/android-viewpager-adapter-update-move-add.html

    相关文章

      网友评论

      本文标题:Viewpager+FragmentStatePagerAdap

      本文链接:https://www.haomeiwen.com/subject/vtwmhftx.html