一、 关于FragmentPagerAdapter 和 FragmentStatePagerAdapter 的数据更新问题
请看https://www.jianshu.com/p/354fbb20ffe3
二、上面的优化存在的问题
ViewPager内部mItems数组缓存了当前可缓存页面的信息。可缓存多少个页面根据mOffscreenPageLimit决定
private final ArrayList mItems = new ArrayList();
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 mFragments = new ArrayList();
@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(val mFragmentManager: FragmentManager):PagerAdapter(){
private val TAG = "FragmentStatePagerAdapt"
private val DEBUG = false
private var mCurTransaction: FragmentTransaction? = null
private val mSavedState = ArrayList()
private var mItemInfos = ArrayList?>()
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(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
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
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).fragment
return fragment.view === view
}
override fun getItemPosition(`object`: Any): Int {
val itemInfo: ItemInfo = `object` as ItemInfo
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?>(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(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) : DynamicFragmentStatePagerAdapter(fm) {
var mDatas = datas
fun setNewData(datas:ArrayList){
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
}