首先當(dāng)然是引出要解決的需求留搔,在做一個(gè)IM模塊時(shí)UI是這樣的:上面是水平的聯(lián)系人欄砌烁,下面是聊天界面,可以水平滑動(dòng)切換聯(lián)系人聊天催式,也就是說是RecyclerView與Viewpager的聯(lián)動(dòng)函喉,為了切換時(shí)體驗(yàn)好需要左右兩邊預(yù)加載多個(gè)聯(lián)系人的聊天內(nèi)容,用戶收到新消息時(shí)當(dāng)前聊天頁變成第一頁(Viewpager頁面要無閃爍或滑動(dòng))頂欄頭像移動(dòng)到第一個(gè)荣月,刪除聊天時(shí)移除當(dāng)前聊天頁其他緩存的頁不變動(dòng)管呵,不閃爍,也就是說頁面位置發(fā)生變化時(shí)已有緩存頁的要用緩存而不是銷毀重建哺窄,大體UI請(qǐng)看圖:
簡書上有朋友讓我寫個(gè)demo出來給他捐下,這不今天抽時(shí)間寫了 :
1. 要實(shí)現(xiàn)的幾個(gè)功能點(diǎn):
- ViewPager中Fragment多少個(gè)不固定账锹,需要?jiǎng)討B(tài)添加,刪除頁面坷襟。
- 更新頁面(Fragment)奸柬,使用已有的緩存頁面。
- 移動(dòng)頁面(Fragment)位置婴程,使用已預(yù)加載的緩存頁面廓奕。
問題:
- 調(diào)用notifyDataSetChanged并沒有去更新內(nèi)容。
- 添加档叔,刪除桌粉,移動(dòng)頁面后會(huì)錯(cuò)位,position與Fragment對(duì)不上衙四。
2. 誤區(qū)
在網(wǎng)上搜索會(huì)發(fā)現(xiàn)铃肯,更新ViewPager的方法基本都是說在apdater的getItemPosition()方法里返回POSITION_NONE,這確實(shí)會(huì)更新传蹈,但也會(huì)導(dǎo)致所有頁面被銷毀重建押逼,會(huì)出現(xiàn)閃爍等問題,性能上也會(huì)不太好惦界。
3. 原理
要做到動(dòng)態(tài)的更新挑格,添加,刪除ViewPager的數(shù)據(jù)表锻,我們需要先弄清ViewPager+adapter是怎么管理頁面的,首先我假設(shè)看此文的你已經(jīng)熟悉了PagerAdapter的各方法作用乞娄,如果沒有的話還請(qǐng)先去查一下瞬逊,這里以FragmentStatePagerAdapter為例,當(dāng)調(diào)用adapter的notifyDataSetChanged后會(huì)通知到viewpager檢查更新數(shù)據(jù):
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會(huì)遍歷它緩存的Item集合确镊,詢問(mAdapter.getItemPosition)Adapter每一個(gè)Item是刪除了(POSITION_NONE),還是更新了位置(ii.position != newPos)范删,還是沒變化(POSITION_UNCHANGED)蕾域,然后對(duì)mItems集合根據(jù)新賦值的position進(jìn)行重新排序。
接下來根據(jù)我們返回的答案進(jìn)行操作到旦,如果是沒變則不做更改旨巷,也就是不會(huì)更新內(nèi)容(PagerAdapter中的getItemPosition默認(rèn)是直接返回POSITION_NONE,所以調(diào)用notifyDataSetChanged默認(rèn)情況下是不會(huì)更新的)添忘,如果是刪除了則從mItems集合中刪除并讓adapter也刪除( mAdapter.destroyItem)采呐,如果是更新了位置則根據(jù)位置的變化對(duì)頁面進(jìn)行更新,會(huì)重新布局(requestLayout)。而在ViewPaer的onLayout方法里會(huì)遍歷每個(gè)子view然后調(diào)用infoForChild()方法從mItems里找到每個(gè)子view對(duì)應(yīng)的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()來判斷某個(gè)ViewPager的子view到底對(duì)應(yīng)的是哪個(gè)Fragment的rootView的搁骑,所以我們一般在adapter的isViewFromObject方法中會(huì)這么寫:
public boolean isViewFromObject(View view, Object object) {
return ((Fragment)object).getView() == view;
}
這樣就把第幾頁要顯示哪個(gè)fragment的view對(duì)應(yīng)上了斧吐,在onlayout時(shí)通過子view位置與itemInfo中的offset(相當(dāng)于偏移到第幾頁)完成了按順序排列頁面又固。
從上面的分析我們知道了adapter的getItemPosition()其實(shí)我們不僅僅只可以返回POSITION_NONE與POSITION_UNCHANGED還可以根據(jù)我們的需要返回一個(gè)更新adapter數(shù)據(jù)后的新位置
總結(jié)一下,ViewPager更新過程分為這幾步:
- adapter調(diào)用notifyDataSetChanged煤率,ViewPager開始檢測更新.
- 通過adapter相應(yīng)的方法詢問出ViewPager中緩存的頁面在新數(shù)據(jù)中的更改情況仰冠。
- 有更新的話重新調(diào)整布局中View的位置。
4. 處理問題1
弄清理了原理蝶糯,那么解決問題1了:我們需要在Viewpager詢問時(shí)告訴ViewPagerItem有哪些變化:
- 緩存中某個(gè)位置的數(shù)據(jù)是否與新數(shù)據(jù)一樣
- 緩存老數(shù)據(jù)在新數(shù)據(jù)中的位置
處理方式參考了此文:FragmentPagerAdapter 和 FragmentStatePagerAdapter 的數(shù)據(jù)更新問題
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中對(duì)當(dāng)前ViewPger中緩存的頁面數(shù)據(jù)進(jìn)行了保存洋只,然后通過三個(gè)抽象方法進(jìn)行對(duì)比:getItemData() ,getDataPosition(),equals()裳涛,使用時(shí)只需要新建一個(gè)Adapter類繼承FixedPagerAdapter完成抽象方法的實(shí)現(xiàn)木张。
這樣做后更新確實(shí)沒問題了,比較科學(xué)端三,不會(huì)閃爍舷礼,全部銷毀,但別忘了我們還要?jiǎng)討B(tài)的添加郊闯,刪除妻献,移動(dòng)位置,這時(shí)如果只是這么處理就會(huì)導(dǎo)致頁面錯(cuò)位問題
原因就是當(dāng)你添加团赁,刪除育拨,移動(dòng)頁面時(shí)ViewPager是通過getItemPosition方法對(duì)它的緩存集合進(jìn)行了對(duì)應(yīng)處理,但我們的FixedPagerAdapter中的緩存并沒有做刪除欢摄,增加位置熬丧,排序。
5. 處理問題2
- 對(duì)FragmentStatePagerAdapter的源碼進(jìn)行修改怀挠,建立一個(gè)OpenFragmentStatePagerAdapter類析蝴,把原來緩存的Fragment List變成緩存我們自己的ItemInfo,ItemInfo中保存了3個(gè)數(shù)據(jù) :fragment绿淋,數(shù)據(jù)和頁面所處的位置闷畸。
- 在instantiateItem ,destroyItem 時(shí)對(duì)List中的ItemInfo進(jìn)行增加吞滞,刪除佑菩,在getItemPosition時(shí)對(duì)list中ItemInfo的position按新數(shù)據(jù)的位置進(jìn)行賦值。
- 在notifyDataSetChanged后和instantiateItem獲取緩存的ItemInfo發(fā)現(xiàn)位置不對(duì)時(shí)進(jìn)行緩存list的排序裁赠,增加等調(diào)整殿漠。
具體請(qǐng)看代碼,重要位置寫了注釋:
/**
* 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) {
//判斷位置是否相等佩捞,如果不相等說明新數(shù)據(jù)有增加或刪除(導(dǎo)致了ViewPager那邊有空位)凸舵,
// 而這時(shí)notifyDataSetChanged方法還沒有完成,ViewPager會(huì)先調(diào)用instantiateItem來獲取新的頁面
//所以為了不取錯(cuò)頁面失尖,我們需要對(duì)緩存進(jìn)行檢查和調(diào)整位置: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中啊奄,以便調(diào)整時(shí)使用
if (oldItemInfo != null) {
oldItemInfo.position = oldDataNewPosition;
}
return oldDataNewPosition;
}
}
return POSITION_UNCHANGED;
}
@Override
public void notifyDataSetChanged() {
super.notifyDataSetChanged();
//通知ViewPager更新完成后對(duì)緩存的ItemInfo List進(jìn)行調(diào)整
checkProcessCacheChanged();
}
private void checkProcessCacheChanged() {
//只有調(diào)用過getItemPosition(也就是有notifyDataSetChanged)才進(jìn)行緩存的調(diào)整
if (!mNeedProcessCache) return;
mNeedProcessCache = false;
ArrayList<ItemInfo<T>> pendingItemInfos = new ArrayList<>(mItemInfos.size());
//先存入空數(shù)據(jù)
for (int i = 0; i < mItemInfos.size(); i++) {
pendingItemInfos.add(null);
}
//根據(jù)緩存的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;
}
}
}
這個(gè)是抽象的基類渐苏,然后我們?cè)诰唧w業(yè)務(wù)代碼處寫一個(gè)Adapter繼承OpenFragmentStatePagerAdapter,就可以用極少的代碼來使用了菇夸,當(dāng)需要更新某個(gè)具體的頁面時(shí)不需要notifyDataSetChanged琼富,只需要用getFragmentByPosition來取出具體頁面,然后局部更新內(nèi)容
使用(因?yàn)轫?xiàng)目中是kotlin寫的庄新,后面有朋友要demo時(shí)寫了個(gè)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)
}
到此鞠眉,我們已經(jīng)可以愉快的使用ViewPager+OpenFragmentStatePagerAdapter來動(dòng)態(tài)添加,刪除择诈,移動(dòng)位置械蹋,更新或局部更新頁面了。源碼及Demo 在下面的github地址中:
作者:竹塵居士
GitHub:https://github.com/homgwu/OpenPagerAdapter
博客:http://zhuchenju.com
公眾號(hào):竹塵居 (zhuchenju92)