目錄
1. 問題:
2. 結論
3. 源碼追蹤分析
(1) PagerAdapter.notifyDataSetChanged() 的源碼:
(2) ViewPager.setAdapter 源碼
(3) ViewPager.PagerObserver 源碼
(4) PagerAdapter.getItemPosition() 源碼
(5)ViewPager. dataSetChanged() 源碼
1. 問題:
每一個ViewPager執行個體對象 都會設定一個PagerAdapter 的執行個體對象.
當資料改變 時,需要調用PagerAdapter.notifyDataSetChanged() 去重新整理界面 (即ViewPager).
但是問題來了: 即使調用了notifyDataSetChanged() , 界面也沒有重新整理,即 PagerAdapter.instantiateItem() 沒有被回調。
2. 結論
解決方法是重寫 PagerAdapter.getItemPosition() 方法, 并傳回 PagerAdapter.POSITION_NONE
@Override
public int getItemPosition(@NonNull Object object) {
return PagerAdapter.POSITION_NONE;
}
3. 源碼追蹤分析
原因:
(1) PagerAdapter.notifyDataSetChanged() 的源碼:
public abstract class PagerAdapter {
/**
* This method should be called by the application if the data backing this adapter has changed
* and associated views should update.
*/
public void notifyDataSetChanged() {
synchronized (this) {
if (mViewPagerObserver != null) {
mViewPagerObserver.onChanged();
}
}
mObservable.notifyChanged();
}
void setViewPagerObserver(DataSetObserver observer) {
synchronized (this) {
mViewPagerObserver = observer;
}
}
}
可以看到,最終會調用 mViewPagerObserver.onChanged()。 那麼這個onChanged 是在哪裡實作的呢???
我們可以看到 mViewPagerObserver 是通過 setViewPagerObserver(DataSetObserver observer) 進行設定的,那麼需要看它是在哪裡被調用的。
回顧我們在設定adapter時有如下代碼:
(2) ViewPager.setAdapter 源碼
public class ViewPager extends ViewGroup {
/**
* Set a PagerAdapter that will supply views for this pager as needed.
*
* @param adapter Adapter to use
*/
public void setAdapter(@Nullable PagerAdapter adapter) {
//....省略其它
final PagerAdapter oldAdapter = mAdapter;
mAdapter = adapter;
mExpectedAdapterCount = 0;
if (mAdapter != null) {
if (mObserver == null) {
mObserver = new PagerObserver();
}
mAdapter.setViewPagerObserver(mObserver);
}
這裡的Adapter.setViewPagerObserver(mObserver) 設定 mViewPagerObserver 的地方。
隻不過這裡傳遞參數的是ViewPager 的内部類 PagerObserver 執行個體對象,而它實作了 DataSetObserver 接口,是以這就對應得上了。
(3) ViewPager.PagerObserver 源碼
private class PagerObserver extends DataSetObserver {
PagerObserver() {
}
@Override
public void onChanged() {
dataSetChanged();
}
@Override
public void onInvalidated() {
dataSetChanged();
}
}
是以,最終的邏輯處理還是在 ViewPager 的方法 dataSetChanged() (放在文章最後,先說明邏輯)
它又會根據 getItemPosition()的傳回值(目前的頁面) 進行處理:
- PagerAdapter.POSITION_UNCHANGED (值為-1), 則不會移除舊的頁面和 加載新的頁面。
- PagerAdapter.POSITION_NONE (值為0), 會移除舊的頁面和 加載新的頁面。
而PagerAdapter的預設實作則是傳回POSITION_UNCHANGED。
(4) PagerAdapter.getItemPosition() 源碼
public int getItemPosition(@NonNull Object object) {
return POSITION_UNCHANGED;
}
是以,我們需要重寫這個函數,并且傳回 PagerAdapter.POSITION_NONE (值為0)
(5)ViewPager. dataSetChanged() 源碼
需要注意變量 newPos 、needPopulate
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 (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;
}
}
if (isUpdating) {
mAdapter.finishUpdate(this);
}
Collections.sort(mItems, COMPARATOR);
if (needPopulate) {
// Reset our known page widths; populate will recompute them.
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.isDecor) {
lp.widthFactor = 0.f;
}
}
setCurrentItemInternal(newCurrItem, false, true);
requestLayout();
}
}