PagerAdapter閃屏坑的修復
背景
最近在填前同事的一個坑時,不小心遇到另外一個坑。 在一個禮物皮膚,原實現是gridView + ViewPager實現的(有幾頁禮物),在送使用者免費禮物時,重新整理ViewPager裡面的item時,出現了閃屏。
其實很多童鞋知道,PagerAdapter在呼叫notifyDataSetChanged(), 如果使用預設的會不起作用
點進notifyDataSetChanged()
/**
* 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();
}
可以看到
- mViewPagerObserver 是怎麼傳進來的呢?該類實際實現類是啥?
搜尋全類只有一處賦值
void setViewPagerObserver(DataSetObserver observer) {
synchronized (this) {
mViewPagerObserver = observer;
}
}
可以看出是PagerObserver類,有ViewPager類初始化setAdapter(PagerAdapter adapter)的時候傳過來。
回到剛才的 mViewPagerObserver.onChanged();PagerObserver的實現如下
@Override
public void onChanged() {
dataSetChanged();
}
恩,所以這裡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 (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();
}
}
這裡的程式碼:
if (newPos == PagerAdapter.POSITION_UNCHANGED) {
continue;
}
if (newPos == PagerAdapter.POSITION_NONE) {
}
恩,明顯是根據PagerAdapter.POSITION_NONE、PagerAdapter.POSITION_UNCHANGED來判斷是否進行更新操作。 PagerAdapter.POSITION_UNCHANGED是什麼時候打上標籤的呢?
哎呀,getItemPosition方法返回的,於是有了解決方法1.
- mObservable.notifyChanged();
/**
* Invokes {@link DataSetObserver#onChanged} on each observer.
* Called when the contents of the data set have changed. The recipient
* will obtain the new contents the next time it queries the data set.
*/
public void notifyChanged() {
synchronized(mObservers) {
// since onChanged() is implemented by the app, it could do anything, including
// removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
}
好吧這裡是逐個通知Observer呼叫onChanged();
解決方案如下:
public int getItemPosition(Object object) {
return PagerAdapter.POSITION_NONE;
}
game over了麼?當然沒有。
上述解決方法只是解決了一個問題,注意測試的話,就會發覺引入了本文標題中提到的閃屏問題~~
到底是哪裡出現的問題呢?前面的我們原始碼都讀的沒有問題,唯一沒注意的就是最後更新的邏輯了。我們再次仔細看看:
注意標箭頭的地方,原來這裡是把整個item remove掉了,難怪會出現閃屏。 事實上我們也可以通過斷點或打log的方式,看本文提到的gridView重新整理時是否複用。
知道了這裡,本文的解決方法如下,使用一個SparseArray來儲存,然後手動重新整理。
class MyPagerAdapter extends PagerAdapter {
private MyGridViewAdapter mGridAdapter;
private SparseArray<GridView> mViews = new SparseArray<>();
@Override
public int getCount() {
if (mInnerAdapter == null || mMaxRows == 0 || mColumns == 0) {
return 0;
}
return (int) Math.ceil(mInnerAdapter.getCount() / (double) (mMaxRows * mColumns));
}
// Remove a page for the given position.
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
mViews.remove(position);
}
// Determines whether a page View is associated with a specific key object as returned by instantiateItem(ViewGroup, int).
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
/**
* PagerAdapter.POSITION_NONE 會導致呼叫notifyDataSetChanged
* 呼叫 destroyItem 導致重新新增item,閃屏的出現
* 但是這裡系統的實現bug, 見ViewPager$PagerObserver
* 預設是POSITION_UNCHANGED 即不重新整理, 呼叫notifyDataSetChanged無反應,
* 這裡使用手動重新整理
*
* @param object
* @return
*/
@Override
public int getItemPosition(Object object) {
int index = -1;
if (mViews != null) {
index = mViews.indexOfValue((GridView) object);
}
return index != -1 ? index : PagerAdapter.POSITION_NONE;
}
@Override
public void notifyDataSetChanged() {
GridView view;
int size = mViews.size();
for (int index = 0; index < size; index++) {
view = mViews.valueAt(index);
if (view != null) {
((MyGridViewAdapter) view.getAdapter()).notifyDataSetChanged();
}
}
super.notifyDataSetChanged();
}
// Create the page for the given position.
@Override
public Object instantiateItem(ViewGroup container, int position) {
GridView mGridView = new GridView(mContext);
....
mGridAdapter = new MyGridViewAdapter(mInnerAdapter, position);
mGridView.setAdapter(mGridAdapter);
container.addView(mGridView);
mViews.put(position, mGridView);
return mGridView;
}
}
相關文章
- w10系統英雄聯盟閃屏怎麼解決_win10英雄聯盟全屏模式閃屏修復方法Win10模式
- Win10 1909系統螢幕頻繁閃屏重新整理的修復方法Win10
- 閃屏頁
- Android 熱修復 - Tinker 實現及踩過的坑Android
- win10 工作管理員修復怎麼操作_windows10工作管理員閃退如何修復Win10Windows
- RevisionFX DEFlicker(ae視訊去閃爍修復外掛)
- win10系統點選資料夾閃退的修復方法Win10
- win10系統ntoskrnl.exe引起藍屏的修復方法Win10
- Flutter 專案的閃屏頁方案Flutter
- 藍屏修復win10步驟 win10藍屏解決辦法Win10
- 電腦藍屏修復按哪個鍵 快速解決電腦藍屏教程
- kernel_data_inpage_error藍屏錯誤怎麼修復Error
- windows10 edge閃退怎麼辦_windows10 edge瀏覽器閃退修復Windows瀏覽器
- win10藍屏程式碼0x00000050怎麼修復_win10藍屏錯誤程式碼0x00000050的修復方法Win10
- dart閃屏成功跳轉Dart
- IOSTips:啟動屏後再加個閃屏的方法iOS
- 【DG】利用閃回資料庫(flashback)修復Failover後的DG環境資料庫AI
- win10開機黃屏怎麼修復_win10電腦黃屏的解決方案Win10
- windows10桌面閃爍怎麼辦_windows10桌面閃爍無法使用修復方法Windows
- 逆向分析及修復稀土掘金iOS版客戶端閃退bugiOS客戶端
- win10頻繁藍屏kernel security check failure修復方法Win10AI
- win10自動修復無法開機藍屏怎麼回事_win10自動修復無法開機且藍屏如何解決Win10
- win10應用商店遊戲閃退怎麼解決_win10微軟商店的遊戲閃退如何修復Win10遊戲微軟
- win10我的電腦閃退怎麼辦_win10開啟此電腦閃退修復方法Win10
- PrestaShop網站漏洞修復如何修復REST網站
- Flutter 專案的閃屏頁方案詳解Flutter
- Asm diskgroup 的修復ASM
- 電腦螢幕一閃一閃怎麼修復 電腦螢幕不停一黑一亮的解決方法
- win10玩dmc鬼泣閃退解決方法_win10鬼泣5黑屏閃退怎麼修復Win10
- win10藍屏當機怎麼辦_win10頻繁藍屏當機修復方法Win10
- win10玩遊戲卡屏怎麼辦 win10玩遊戲卡屏當機修復方法Win10遊戲
- Android 手遊閃屏極簡方案Android
- lol關於win10系統導致閃退崩潰修復方法Win10
- #兩年移動端踩坑,遇到的那些不得不說的bug及修復
- win10更新與安全閃退怎麼辦_win10點更新與安全就閃退修復方法Win10
- win10應用程式閃退怎麼解決 win10開啟軟體閃退如何修復Win10
- win10軟體商店閃退怎麼辦 win10軟體商店開啟閃退修復方法Win10
- win10系統頻繁藍屏critical process died怎麼修復Win10