前言
最近在封裝輪播圖的時候,為了提高性能我使用了緩存,将每個被釋放的view進行緩存然後在下次要用到是直接使用緩存的Veiw。這樣做的好處就是不會一直inflate布局,也不會有一直設定圖檔标題等指派操作。先來看下我PagerAdapter中的關鍵代碼吧。
/**
* 用來放置可以複用的頁面的View。
*/
private SparseArray<View> itemViewCache = new SparseArray<>();
@Override
public Object instantiateItem(ViewGroup container, int position) {
int index = getIndex(position);
View entryView = itemViewCache.get(index);
if (entryView == null) {
BannerEntry bannerEntry = mItems.get(index);
entryView = bannerEntry.onCreateView(container);
entryView.setTag(KEY_INDEX_TAG, index);
entryView.setOnClickListener(this);
entryView.setOnLongClickListener(this);
entryView.setOnTouchListener(mTouchListener);
} else {
itemViewCache.remove(index);
}
container.addView(entryView);
return entryView;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
View view = (View) object;
container.removeView(view);
int index = getIndex(position);
if (itemViewCache.get(index) == null) {
itemViewCache.put(index, view);
}
}
就是每次
public Object instantiateItem(ViewGroup container, int position)
方法執行的時候就去擷取緩存的view,如果沒有緩存的view則建立一個這裡的建立包括了填充布局和設定圖檔。如果有緩存的則将這個View從緩存池中移除。然後執行ViewPager的addVeiw()方法。然後在
public void destroyItem(ViewGroup container, int position, Object object)
方法執行的時候先removeView,然後将被remove的View進行緩存,以索引為key,View為值。網上許多VeiwPager的緩存方式和我都大同小異。就不在多說了。
Bug描述
在通過上面的方式使用緩存的View後再通常情況下是沒有問題的,隻有一種情況下有問題,就是給VeiwPager通過
public void setPageTransformer(boolean reverseDrawingOrder, PageTransformer transformer)
方法設定自定義翻頁動畫并且這個動畫在默寫時候會出現重疊這個時候Bug就産生了,上面Bug呢,就是會出現某一張蓋在另一張上導緻順序錯亂。下面通過一張Gif圖檔說明問題。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0NXYFhGd192UvwVe0lmdhJ3ZvwFM38CXlZHbvN3cpR2Lc1TPB10QGtWUCpEMJ9CXsxWam9CXwADNvwVZ6l2c052bm9CXUJDT1wkNhVzLcRnbvZ2Lc1TQU1EM0cVYzZlMhZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39DOwkzNxkzM1EDNxkDM3EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
從上面的圖檔中可以看出在動畫的執行過程中第一、第二、第四張翻頁到下一張都是正常的,隻有第三張被加載之後第四張會出現在第三張的上面,并且會搶奪焦點和點選事件。如果你也遇到過我這樣的問題不一定會和我出現問題的順序一樣,但是是有規律的,具體是哪一張有問題,我不知道怎麼描述(語言組織能力差),這裡就不說了。下面說下導緻這個Bug的原因吧。
導緻的原因及解決辦法
一開始我也不知道原因是什麼,我就想難道是我複用View的原因,然後我就把複用View的代碼注掉,不讓它複用,每次加載頁面都重新建立。結果就真的沒有這個問題了,然後我就想,是不是我通過
container.addView(entryView);
方法addVeiw之後ViewPager幫我做了什麼事情改變了我的Veiw?于是我就翻起了源碼,翻過ViewPager源碼的應該都知道,代碼還挺多了,3000+行。而且裡面有些關鍵代碼是看不到源碼的。So,我沒有找到原因。
後來我通過打斷點的方式看看我新創的View被加進去之前的參數和被remove之後有什麼差別,通過對比我發現基本都一樣,隻有LayoutParams中的兩個字段發生了變化引起了我的注意,分别為“position”和“widthFactor”,我發現新建立的View被add之前position都等于0,widthFactor都等于0.0。被remove之後position都等于
public void destroyItem(ViewGroup container, int position, Object object)
方法中的position參數,widthFactor基本都等于1.0。然後我想在remove之後将這兩個參數的值重置會不會就好了呢,果然,問題解決了。至于為什麼在destroyItem的時候ViewPager沒有幫我重置,也沒有提供api讓我重置我就不清楚了,下面奉上代碼,因為這兩個字段都是本地成員是以要用反射的方式去修改字段。
private void reSetLayoutParams(ViewPager.LayoutParams lp) {
try {
Field positionField = getField(ViewPager.LayoutParams.class, "position");
if (positionField != null) {
positionField.setInt(lp, );
}
Field widthFactorField = getField(ViewPager.LayoutParams.class, "widthFactor");
if (widthFactorField != null) {
widthFactorField.setFloat(lp, f);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private Field getField(Class cls,String fieldName) {
Field positionField = null;
try {
positionField =cls.getDeclaredField(fieldName);
positionField.setAccessible(true);
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
return positionField;
}
定義好上面的方法然後在destroyItem的時候調用就OK了。下面是destroyItem方法的最終代碼:
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
View view = (View) object;
container.removeView(view);
reSetLayoutParams((ViewPager.LayoutParams) view.getLayoutParams());
int index = getIndex(position);
if (itemViewCache.get(index) == null) {
itemViewCache.put(index, view);
}
}
下面奉上最終的效果圖:
CSDN圖檔大小限制2M,想了解更多的歡迎光顧GitHub。
源碼GitHub位址
GitHub位址傳送門 如果您覺得本篇文章有用請幫忙點贊,如果有上面疑問也可以在下方評論區留言。如果你喜歡我的項目,Start和Fork都是對我的支援,您的支援是對我最大的鼓勵。謝謝!!!