美文网首页
高效动态FragmentPagerAdapter的优化

高效动态FragmentPagerAdapter的优化

作者: 爱学习的小宝宝 | 来源:发表于2019-12-19 16:56 被阅读0次

    一、 关于FragmentPagerAdapter 和 FragmentStatePagerAdapter 的数据更新问题

    请看https://www.jianshu.com/p/354fbb20ffe3

    二、上面的优化存在的问题

    ViewPager内部mItems数组缓存了当前可缓存页面的信息。可缓存多少个页面根据mOffscreenPageLimit决定

     private final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>();
     static class ItemInfo {
            Object object;//instantiateItem所返回的对象
            int position;//页面的index
            boolean scrolling;
            float widthFactor;
            float offset;
        }
    

    当页面进行切换的时候。根据当前页面的位置和mOffscreenPageLimit做其他页面的增删操作

     void populate(int newCurrentItem) {
      .......
    
      final int pageLimit = mOffscreenPageLimit;
      //缓存页面起始位置
      final int startPos = Math.max(0, mCurItem - pageLimit);
      final int N = mAdapter.getCount();
      //缓存页面结束位置
      final int endPos = Math.min(N - 1, mCurItem + pageLimit);
      ........
    
      int curIndex = -1;
      ItemInfo curItem = null;
      //找到当前位置的itemInfo
      for (curIndex = 0; curIndex < mItems.size(); curIndex++) {
            final ItemInfo ii = mItems.get(curIndex);
            if (ii.position >= mCurItem) {
                if (ii.position == mCurItem) curItem = ii;
                    break;
                }
            }
      //不存在则添加
      if (curItem == null && N > 0) {
            curItem = addNewItem(mCurItem, curIndex);
        }
    
      //处理左边的页面
      if (curItem != null) {
          float extraWidthLeft = 0.f;
          //当前页面左边的index
          int itemIndex = curIndex - 1;
          //如果左边有页面
          ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
          final int clientWidth = getClientWidth();
          final float leftWidthNeeded = clientWidth <= 0 ? 0 :
             2.f - curItem.widthFactor + (float) getPaddingLeft() / (float) clientWidth;
          //由当前页面向左遍历
          for (int pos = mCurItem - 1; pos >= 0; pos--) {
           // 如果左边的宽度超过了所需的宽度,并且当前当前页面位置比第一个缓存页面位置小
              if (extraWidthLeft >= leftWidthNeeded && pos < startPos) {
                     //左边无页面,直接跳出
                     if (ii == null) {
                          break;
                      }
                     //左边有页面进行销毁
                     if (pos == ii.position && !ii.scrolling) {
                           mItems.remove(itemIndex);
                           mAdapter.destroyItem(this, pos, ii.object);
                           if (DEBUG) {
                               Log.i(TAG, "populate() - destroyItem() with pos: " + pos
                                        + " view: " + ((View) ii.object));
                           }                
                           itemIndex--;
                            //移除后当前索引减1
                           curIndex--;
                           ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
                      }
                 } else if (ii != null && pos == ii.position) {
                    //如果当前位置是需要缓存的位置,并且这个位置上的页面已经存在
                    //则将左边宽度加上当前位置的页面
                     extraWidthLeft += ii.widthFactor;
                    //继续向左遍历
                     itemIndex--;
                     ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
                 } else {
                    //itemInfo不存在。并且位置大于最小缓存位置。则添加
                     ii = addNewItem(pos, itemIndex + 1);
                    //将左边宽度加上当前位置的页面
                     extraWidthLeft += ii.widthFactor;
                    //添加后当前索引加1
                     curIndex++;
                     ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
                 }
             }
            //右边的页面同理
           ...........
    
    }
    

    FragmentStatePagerAdapter里缓存了当前已加载的fragment,如果缓存里对应位置的fragement存在则直接返回

      private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
      @NonNull
      @Override
      public Object instantiateItem(@NonNull 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 (mFragments.size() > position) {
                Fragment f = mFragments.get(position);
                if (f != null) {
                    return f;
                }
            }
    
            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 (mFragments.size() <= position) {
                mFragments.add(null);
            }
            fragment.setMenuVisibility(false);
            fragment.setUserVisibleHint(false);
            mFragments.set(position, fragment);
            mCurTransaction.add(container.getId(), fragment);
    
            return fragment;
     }
       @Override
        public void destroyItem(@NonNull ViewGroup container, int position, @NonNull 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);
    
            mCurTransaction.remove(fragment);
        }
    

    上面优化方案并不支持增删更新,而且数据相同时当新数据的位置发生改变后。ViewPager里的mItems数组ItemInfo的postion发生改变。此时并没有通知adapter里的mFragments进行更新。当ItemInfo的position更新后滑动页面需要destory其他的postion时。mFragments可能会数组越界。

    三、支持增删动态更新的优化方案

    重写FragmentStatePagerAdater,缓存数组同样记录历史数据位置,使adapter缓存数组与ViewPager里的mItem数组同步更新。在数据进行更新的时候对缓存数组位置以及大小进行更新

    abstract class DynamicFragmentStatePagerAdapter<T>(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
    
      /**
        * 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
    
        //根据位置获取数据需要加数组越界判断,外部数据移除后,调用notifyDataSetChanged
        //ViewPager通过getItemPosition来判断老数据位置是否更新的同时会通过老的postion来获取
        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)
    
    
        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 {
                        //由于先调用notifyDataSetChanged,ViewPager的ItemInfo.postion发生改变后,可能会优先调用instantiateItem添加新的页面
                        // 所以要做位置判断,如果不正确则更新数组后再返回
                        if (it.position == position) {
                            if(!it.fragment.isAdded){
                                if (mCurTransaction == null) {
                                    mCurTransaction = mFragmentManager.beginTransaction()
                                }
                                mCurTransaction?.add(container.id, it.fragment)
                            }
                            return it
                        } else {
                            checkDataUpdate()
                            return instantiateItem(container,position)
    
                        }
                    }
                }
    
                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 {
                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)) {
                        POSITION_UNCHANGED
                    } else {
                        var oldDataNewPosition = getDataPosition(oldData)
                        if (oldDataNewPosition < 0) {
                            oldDataNewPosition =POSITION_NONE
                        }
                        itemInfo.apply {
                            position = oldDataNewPosition
                        }
                        oldDataNewPosition
                    }
                }
                return POSITION_UNCHANGED
            }
    
         override fun notifyDataSetChanged() {
                super.notifyDataSetChanged()
                //更新缓存数组
                checkDataUpdate()
            }
          /**
            * 更新缓存数组。使位置和大小对应
            */
          private fun checkDataUpdate() {
                val pendingItemInfos = ArrayList<ItemInfo<T>?>(mItemInfos.size)
                //添加空数据
                for (i in 0 until mItemInfos.size) {
                    pendingItemInfos.add(null)
                }
                for (value in mItemInfos) {
                    value?.apply {
                        if (position >= 0) {
                            //个数小于等于postion,需要继续添加空数据
                            while (pendingItemInfos.size <= position) {
                                pendingItemInfos.add(null)
                            }
                            //放到对应的位置
                            pendingItemInfos[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
            }
           
    }
    

    四、使用

       class MyFragmentAdapter(fm:FragmentManager,datas:ArrayList<String>) : DynamicFragmentStatePagerAdapter<String>(fm) {
    
            var mDatas=datas
    
            fun setNewData(datas:ArrayList<String>){
                this.mDatas=datas
                notifyDataSetChanged()
            }
    
            fun addData(data:String){
                mDatas.add(data)
                notifyDataSetChanged()
            }
    
            fun addData(data:String,position: Int){
                mDatas.add(position,data)
                notifyDataSetChanged()
            }
    
            fun remove(position: Int){
                mDatas.removeAt(position)
                notifyDataSetChanged()
    
            }
    
            fun move(from:Int,to:Int){
                if (from == to) return
                Collections.swap(mDatas, from, to)
                notifyDataSetChanged()
            }
    
            fun update(position: Int,data: String){
                if (position >= 0 && mDatas.size > position) {
                    mDatas[position] = data
                    notifyDataSetChanged()
                }
            }
    
            override fun dataEquals(oldData: String?, newData: String?)=TextUtils.equals(oldData, newData)
    
            override fun getItemData(position: Int):String?{
                return if(position<mDatas.size){
                    mDatas[position]
                }else{
                    null
                }
            }
    
            override fun getDataPosition(t: String?)=mDatas.indexOf(t)
    
            override fun getItem(position: Int):Fragment{
    
                return ItemFragment().apply {
                    arguments=Bundle().apply {
                        putString("text",mDatas[position])
                    }
                }
            }
    
            override fun getCount()=mDatas.size
    
        }
    

    相关文章

      网友评论

          本文标题:高效动态FragmentPagerAdapter的优化

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