【Android】從源碼分析PagerAdapter/FragmentPagerAdapter調(diào)用notifydataSetChanged()刷新的原理
來源:程序員人生 發(fā)布時間:2015-05-13 08:19:58 閱讀次數(shù):5642次
相信譽過viewpager的同學(xué)都會遇到調(diào)用notifydataSetChanged()后不刷新或不符合預(yù)期的問題,今天就來分析分析這里的來龍去脈。這1切還得從viewpager的setAdapter說起:
/**
* Set a PagerAdapter that will supply views for this pager as needed.
*
* @param adapter Adapter to use
*/
public void setAdapter(PagerAdapter adapter) {
...(省略若干行,下同)
...
final PagerAdapter oldAdapter = mAdapter;
mAdapter = adapter;
mExpectedAdapterCount = 0;
if (mAdapter != null) {
if (mObserver == null) {
mObserver = new PagerObserver();
}
mAdapter.registerDataSetObserver(mObserver);
...
...
}
...
}
mAdapter.registerDataSetObserver(mObserver)這里用到了視察者模式,mObserver是PagerObserver的1個實例,而PagerObserver是ViewPager的1個內(nèi)部類,其聲明以下:
private class PagerObserver extends DataSetObserver {
@Override
public void onChanged() {
//這里調(diào)用了viewpager的dataSetChanged()方法,下同
dataSetChanged();
}
@Override
public void onInvalidated() {
dataSetChanged();
}
}
所以當(dāng)mAdapter數(shù)據(jù)有變化調(diào)用notifydatasetchanged()刷新時就會調(diào)用到PagerObserver 的onChanged方法,繼而調(diào)用到了viewpager的dataSetChanged()方法:
void dataSetChanged() {
// This method only gets called if our observer is attached, so mAdapter is non-null.
final int adapterCount = mAdapter.getCount();
mExpectedAdapterCount = adapterCount;
boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1 &&
mItems.size() < adapterCount;
int newCurrItem = mCurItem;
boolean isUpdating = false;
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 (needPopulate) {
...
...
setCurrentItemInternal(newCurrItem, false, true);
requestLayout();
}
}
這里看到,調(diào)用mAdapter.getItemPosition,如果返回的值是POSITION_UNCHANGED(PagerAdapter的默許實現(xiàn)),則needPopulate為false,就不會調(diào)用到setCurrentItemInternal(里面間接調(diào)用到instantiateItem(),后面講到),所以就不會刷新視圖。反之,如果返回值是POSITION_NONE,則needPopulate為true,就會調(diào)用到setCurrentItemInternal:
void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {
...
populate(item);
...
}
而populate(item)里面會調(diào)用到addNewItem:
ItemInfo addNewItem(int position, int index) {
ItemInfo ii = new ItemInfo();
ii.position = position;
ii.object = mAdapter.instantiateItem(this, position);
ii.widthFactor = mAdapter.getPageWidth(position);
if (index < 0 || index >= mItems.size()) {
mItems.add(ii);
} else {
mItems.add(index, ii);
}
return ii;
}
這里看到調(diào)用到了mAdapter.instantiateItem(this, position);而PagerAdapter的instantiateItem里甚么都沒做。
所以如果我們用的是PagerAdapter,我們需要復(fù)寫instantiateitem,例如我們可以這么寫:
@Override
public Object instantiateItem(ViewGroup view, int position) {
view.addView(mList.get(position));
return mList.get(position);
}
而對FragmentPagerAdapter,它復(fù)寫了instantiateitem:
@Override
public Object instantiateItem(ViewGroup container, int position) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
final long itemId = getItemId(position);
// Do we already have this fragment?
String name = makeFragmentName(container.getId(), itemId);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
mCurTransaction.attach(fragment);
} else {
fragment = getItem(position);
if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), itemId));
}
if (fragment != mCurrentPrimaryItem) {
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
}
return fragment;
}
其中,getItemId的默許實現(xiàn):
public long getItemId(int position) {
return position;
}
也就是item對應(yīng)的下標(biāo)就是item的id。另外makeFragmentName的實現(xiàn):
private static String makeFragmentName(int viewId, long id) {
return "android:switcher:" + viewId + ":" + id;
}
這里makeFragmentName相當(dāng)于為這個fragment組裝成1個標(biāo)識Tag,如果之前沒添加過這個fragment,也就是mFragmentManager.findFragmentByTag(name)返回null,那末就調(diào)用 getItem(position);獲得fragment,然后把這個fragment添加進去。
反之,也就是之前已添加過這個fragment了,則不會調(diào)用 getItem(position)了,而是直接attach上這個fragment。
參考
http://www.cnblogs.com/dancefire/archive/2013/01/02/why-notifydatasetchanged-does-not-work.html
生活不易,碼農(nóng)辛苦
如果您覺得本網(wǎng)站對您的學(xué)習(xí)有所幫助,可以手機掃描二維碼進行捐贈