一既绕、 關(guān)于FragmentPagerAdapter 和 FragmentStatePagerAdapter 的數(shù)據(jù)更新問題
請看http://www.reibang.com/p/354fbb20ffe3
二震缭、上面的優(yōu)化存在的問題
ViewPager內(nèi)部mItems數(shù)組緩存了當前可緩存頁面的信息邪乍√鳎可緩存多少個頁面根據(jù)mOffscreenPageLimit決定
private final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>();
static class ItemInfo {
Object object;//instantiateItem所返回的對象
int position;//頁面的index
boolean scrolling;
float widthFactor;
float offset;
}
當頁面進行切換的時候皆刺。根據(jù)當前頁面的位置和mOffscreenPageLimit做其他頁面的增刪操作
void populate(int newCurrentItem) {
.......
final int pageLimit = mOffscreenPageLimit;
//緩存頁面起始位置
final int startPos = Math.max(0, mCurItem - pageLimit);
final int N = mAdapter.getCount();
//緩存頁面結(jié)束位置
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) {
//如果當前位置是需要緩存的位置寿羞,并且這個位置上的頁面已經(jīng)存在
//則將左邊寬度加上當前位置的頁面
extraWidthLeft += ii.widthFactor;
//繼續(xù)向左遍歷
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绪穆,如果緩存里對應(yīng)位置的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);
}
上面優(yōu)化方案并不支持增刪更新辨泳,而且數(shù)據(jù)相同時當新數(shù)據(jù)的位置發(fā)生改變后虱岂。ViewPager里的mItems數(shù)組ItemInfo的postion發(fā)生改變。此時并沒有通知adapter里的mFragments進行更新菠红。當ItemInfo的position更新后滑動頁面需要destory其他的postion時第岖。mFragments可能會數(shù)組越界。
三试溯、支持增刪動態(tài)更新的優(yōu)化方案
重寫FragmentStatePagerAdater蔑滓,緩存數(shù)組同樣記錄歷史數(shù)據(jù)位置,使adapter緩存數(shù)組與ViewPager里的mItem數(shù)組同步更新遇绞。在數(shù)據(jù)進行更新的時候?qū)彺鏀?shù)組位置以及大小進行更新
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
//根據(jù)位置獲取數(shù)據(jù)需要加數(shù)組越界判斷键袱,外部數(shù)據(jù)移除后,調(diào)用notifyDataSetChanged
//ViewPager通過getItemPosition來判斷老數(shù)據(jù)位置是否更新的同時會通過老的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 {
//由于先調(diào)用notifyDataSetChanged摹闽,ViewPager的ItemInfo.postion發(fā)生改變后蹄咖,可能會優(yōu)先調(diào)用instantiateItem添加新的頁面
// 所以要做位置判斷,如果不正確則更新數(shù)組后再返回
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()
//更新緩存數(shù)組
checkDataUpdate()
}
/**
* 更新緩存數(shù)組付鹿。使位置和大小對應(yīng)
*/
private fun checkDataUpdate() {
val pendingItemInfos = ArrayList<ItemInfo<T>?>(mItemInfos.size)
//添加空數(shù)據(jù)
for (i in 0 until mItemInfos.size) {
pendingItemInfos.add(null)
}
for (value in mItemInfos) {
value?.apply {
if (position >= 0) {
//個數(shù)小于等于postion澜汤,需要繼續(xù)添加空數(shù)據(jù)
while (pendingItemInfos.size <= position) {
pendingItemInfos.add(null)
}
//放到對應(yīng)的位置
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
}