您好,登錄后才能下訂單哦!
Android中列表的復用機制提高了APP的運行效率,但隨之而來的復用的問題總是讓程序員們頭痛,一個
bug找頭天也找不到。我就把自己解決這方面的經驗貢獻出來供大家參考:
問題1:什么是復用
復用其實指的是復用View,而綁定View的數據是變化的。
問題2:復用的原理探究
為了徹底弄清楚復用的原理,和特地寫了段小程序。
Adapter代碼:
class MyAdapter extends BaseAdapter{ @Override public int getCount() { return 20; } @Override public Object getItem(int position) { return null; } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { Log.i(TAG, "aaaaaaaaaa---------- getView: position = " + position + ",convertView = " + convertView); ViewHolder holder; if(convertView == null){ convertView = LayoutInflater.from(SimpleCheckBoxListActivity.this).inflate(R.layout.adapter_simple_checkbox_item,null,false); holder = new ViewHolder(); holder.tv = (TextView) convertView.findViewById(R.id.tv); holder.cb = (CheckBox) convertView.findViewById(R.id.cb); convertView.setTag(holder); }else{ holder = (ViewHolder) convertView.getTag(); } holder.tv.setText("index = " + position); Log.i(TAG, "bbbbbbbbbb---------- getView: position = " + position + ",convertView = " + convertView.toString()); //將convertView緩存起來,方便后面的分析。 itemViews.put(position,convertView); //分析當前position是否復用了之前哪個位置的view int reusePosition = analyseReusedWhichPosition(position); if(reusePosition != -1){ Log.i(TAG, "getView: 位置 " + position + "復用了位置" + reusePosition + "的view"); } return convertView; } class ViewHolder{ TextView tv; CheckBox cb; } //分析當前position是否復用了之前哪個位置的view private int analyseReusedWhichPosition(int currentPosition){ View currentPositionView = itemViews.get(currentPosition); for (int i = 0; i < currentPosition; i++) { View beforePositionView = itemViews.get(i); if(beforePositionView == null){ continue; } if(beforePositionView == currentPositionView){ return i; } } return -1; } }
日志分析:
1)程序初次運行
打印的日志:
aaaaaaaaaa---------- getView: position = 0,convertView = null bbbbbbbbbb---------- getView: position = 0,convertView = android.widget.LinearLayout{42eceab0 V.E..... ......I. 0,0-0,0} aaaaaaaaaa---------- getView: position = 1,convertView = null bbbbbbbbbb---------- getView: position = 1,convertView = android.widget.LinearLayout{42ee4650 V.E..... ......I. 0,0-0,0} aaaaaaaaaa---------- getView: position = 2,convertView = null bbbbbbbbbb---------- getView: position = 2,convertView = android.widget.LinearLayout{42ee6140 V.E..... ......I. 0,0-0,0} aaaaaaaaaa---------- getView: position = 3,convertView = null bbbbbbbbbb---------- getView: position = 3,convertView = android.widget.LinearLayout{42ee7c10 V.E..... ......I. 0,0-0,0} aaaaaaaaaa---------- getView: position = 4,convertView = null bbbbbbbbbb---------- getView: position = 4,convertView = android.widget.LinearLayout{42ee96e0 V.E..... ......I. 0,0-0,0} aaaaaaaaaa---------- getView: position = 5,convertView = null bbbbbbbbbb---------- getView: position = 5,convertView = android.widget.LinearLayout{42eeb1e8 V.E..... ......I. 0,0-0,0} aaaaaaaaaa---------- getView: position = 6,convertView = null bbbbbbbbbb---------- getView: position = 6,convertView = android.widget.LinearLayout{42eeccb8 V.E..... ......I. 0,0-0,0} aaaaaaaaaa---------- getView: position = 7,convertView = null bbbbbbbbbb---------- getView: position = 7,convertView = android.widget.LinearLayout{42eee788 V.E..... ......I. 0,0-0,0}
2)接著向下滑動,索引0沒有完全消失,索引8就出現了,這時還沒有復用。
打印的日志:
aaaaaaaaaa---------- getView: position = 8,convertView = null bbbbbbbbbb---------- getView: position = 8,convertView = android.widget.LinearLayout{42ef5150 V.E..... ......I. 0,0-0,0}
3)位置9出現,索引0已經完全消失(復用開始出現)
打印的日志:
aaaaaaaaaa---------- getView: position = 9,convertView = android.widget.LinearLayout{42eceab0 V.E..... ........ 0,-215-1080,1} bbbbbbbbbb---------- getView: position = 9,convertView = android.widget.LinearLayout{42eceab0 V.E..... .......D 0,-215-1080,1} getView: 位置 9復用了位置0的view 可以發現索引9處的hashCode與索引0處的hashCode都是42eceab0
4)緊接著向下滾動到最后(注意是慢慢地滾動)
打印的日志:
aaaaaaaaaa---------- getView: position = 10,convertView = android.widget.LinearLayout{42ee4650 V.E..... ........ 0,-213-1080,3} bbbbbbbbbb---------- getView: position = 10,convertView = android.widget.LinearLayout{42ee4650 V.E..... .......D 0,-213-1080,3} getView: 位置 10復用了位置1的view aaaaaaaaaa---------- getView: position = 11,convertView = android.widget.LinearLayout{42ee6140 V.E..... ........ 0,-205-1080,11} bbbbbbbbbb---------- getView: position = 11,convertView = android.widget.LinearLayout{42ee6140 V.E..... .......D 0,-205-1080,11} getView: 位置 11復用了位置2的view aaaaaaaaaa---------- getView: position = 12,convertView = android.widget.LinearLayout{42ee7c10 V.E..... ........ 0,-202-1080,14} bbbbbbbbbb---------- getView: position = 12,convertView = android.widget.LinearLayout{42ee7c10 V.E..... .......D 0,-202-1080,14} getView: 位置 12復用了位置3的view aaaaaaaaaa---------- getView: position = 13,convertView = android.widget.LinearLayout{42ee96e0 V.E..... ........ 0,-201-1080,15} bbbbbbbbbb---------- getView: position = 13,convertView = android.widget.LinearLayout{42ee96e0 V.E..... .......D 0,-201-1080,15} getView: 位置 13復用了位置4的view aaaaaaaaaa---------- getView: position = 14,convertView = android.widget.LinearLayout{42eeb1e8 V.E..... ........ 0,-188-1080,28} bbbbbbbbbb---------- getView: position = 14,convertView = android.widget.LinearLayout{42eeb1e8 V.E..... .......D 0,-188-1080,28} getView: 位置 14復用了位置5的view aaaaaaaaaa---------- getView: position = 15,convertView = android.widget.LinearLayout{42eeccb8 V.E..... ........ 0,-213-1080,3} bbbbbbbbbb---------- getView: position = 15,convertView = android.widget.LinearLayout{42eeccb8 V.E..... .......D 0,-213-1080,3} getView: 位置 15復用了位置6的view aaaaaaaaaa---------- getView: position = 16,convertView = android.widget.LinearLayout{42eee788 V.E..... ........ 0,-179-1080,37} bbbbbbbbbb---------- getView: position = 16,convertView = android.widget.LinearLayout{42eee788 V.E..... .......D 0,-179-1080,37} getView: 位置 16復用了位置7的view aaaaaaaaaa---------- getView: position = 17,convertView = android.widget.LinearLayout{42ef5150 V.E..... ........ 0,-181-1080,35} bbbbbbbbbb---------- getView: position = 17,convertView = android.widget.LinearLayout{42ef5150 V.E..... .......D 0,-181-1080,35} getView: 位置 17復用了位置8的view aaaaaaaaaa---------- getView: position = 18,convertView = android.widget.LinearLayout{42eceab0 V.E..... ........ 0,-195-1080,21} bbbbbbbbbb---------- getView: position = 18,convertView = android.widget.LinearLayout{42eceab0 V.E..... .......D 0,-195-1080,21} getView: 位置 18復用了位置0的view aaaaaaaaaa---------- getView: position = 19,convertView = android.widget.LinearLayout{42ee4650 V.E..... ........ 0,-210-1080,6} bbbbbbbbbb---------- getView: position = 19,convertView = android.widget.LinearLayout{42ee4650 V.E..... .......D 0,-210-1080,6} getView: 位置 19復用了位置1的view
可以看到向下慢慢滑動的時候,復用是很有規律的。
但是如果快速的向下滑動的時候,又發現不了什么規律:復用并非是連續的
aaaaaaaaaa---------- getView: position = 8,convertView = android.widget.LinearLayout{42f9a780 V.E..... ........ 0,-85-1080,131} bbbbbbbbbb---------- getView: position = 8,convertView = android.widget.LinearLayout{42f9a780 V.E..... .......D 0,-85-1080,131} getView: 位置 8復用了位置0的view aaaaaaaaaa---------- getView: position = 9,convertView = android.widget.LinearLayout{42f9f818 V.E..... ........ 0,384-1080,600} bbbbbbbbbb---------- getView: position = 9,convertView = android.widget.LinearLayout{42f9f818 V.E..... .......D 0,384-1080,600} getView: 位置 9復用了位置3的view aaaaaaaaaa---------- getView: position = 10,convertView = android.widget.LinearLayout{42f9dd48 V.E..... ........ 0,138-1080,354} bbbbbbbbbb---------- getView: position = 10,convertView = android.widget.LinearLayout{42f9dd48 V.E..... .......D 0,138-1080,354} getView: 位置 10復用了位置2的view aaaaaaaaaa---------- getView: position = 11,convertView = android.widget.LinearLayout{42f9c278 V.E..... ........ 0,-108-1080,108} bbbbbbbbbb---------- getView: position = 11,convertView = android.widget.LinearLayout{42f9c278 V.E..... .......D 0,-108-1080,108} getView: 位置 11復用了位置1的view aaaaaaaaaa---------- getView: position = 12,convertView = null bbbbbbbbbb---------- getView: position = 12,convertView = android.widget.LinearLayout{42fad3a0 V.E..... ......I. 0,0-0,0} aaaaaaaaaa---------- getView: position = 13,convertView = android.widget.LinearLayout{42fa2df0 V.E..... ........ 0,60-1080,276} bbbbbbbbbb---------- getView: position = 13,convertView = android.widget.LinearLayout{42fa2df0 V.E..... .......D 0,60-1080,276} getView: 位置 13復用了位置5的view aaaaaaaaaa---------- getView: position = 14,convertView = android.widget.LinearLayout{42fa12e8 V.E..... ........ 0,-186-1080,30} bbbbbbbbbb---------- getView: position = 14,convertView = android.widget.LinearLayout{42fa12e8 V.E..... .......D 0,-186-1080,30} getView: 位置 14復用了位置4的view aaaaaaaaaa---------- getView: position = 15,convertView = android.widget.LinearLayout{42fa48c0 V.E..... ........ 0,-150-1080,66} bbbbbbbbbb---------- getView: position = 15,convertView = android.widget.LinearLayout{42fa48c0 V.E..... .......D 0,-150-1080,66} getView: 位置 15復用了位置6的view aaaaaaaaaa---------- getView: position = 16,convertView = android.widget.LinearLayout{42f9a780 V.E..... ........ 0,78-1080,294} bbbbbbbbbb---------- getView: position = 16,convertView = android.widget.LinearLayout{42f9a780 V.E..... .......D 0,78-1080,294} getView: 位置 16復用了位置0的view aaaaaaaaaa---------- getView: position = 17,convertView = android.widget.LinearLayout{42f9f818 V.E..... ........ 0,13-1080,229} bbbbbbbbbb---------- getView: position = 17,convertView = android.widget.LinearLayout{42f9f818 V.E..... .......D 0,13-1080,229} getView: 位置 17復用了位置3的view aaaaaaaaaa---------- getView: position = 18,convertView = android.widget.LinearLayout{42f9dd48 V.E..... ........ 0,-28-1080,188} bbbbbbbbbb---------- getView: position = 18,convertView = android.widget.LinearLayout{42f9dd48 V.E..... .......D 0,-28-1080,188} getView: 位置 18復用了位置2的view aaaaaaaaaa---------- getView: position = 19,convertView = android.widget.LinearLayout{42fa6390 V.E..... ........ 0,-168-1080,48} bbbbbbbbbb---------- getView: position = 19,convertView = android.widget.LinearLayout{42fa6390 V.E..... .......D 0,-168-1080,48} getView: 位置 19復用了位置7的view
5)最后,向上滾動到索引為0的位置
aaaaaaaaaa---------- getView: position = 11,convertView = android.widget.LinearLayout{4304de70 V.E..... ........ 0,-212-1080,4} bbbbbbbbbb---------- getView: position = 11,convertView = android.widget.LinearLayout{4304de70 V.E..... .......D 0,-212-1080,4} getView: 位置 11復用了位置8的view aaaaaaaaaa---------- getView: position = 10,convertView = android.widget.LinearLayout{4303ee70 V.E..... ........ 0,1829-1080,2045} bbbbbbbbbb---------- getView: position = 10,convertView = android.widget.LinearLayout{4303ee70 V.E..... .......D 0,1829-1080,2045} getView: 位置 10復用了位置1的view aaaaaaaaaa---------- getView: position = 9,convertView = android.widget.LinearLayout{43040940 V.E..... ........ 0,1829-1080,2045} bbbbbbbbbb---------- getView: position = 9,convertView = android.widget.LinearLayout{43040940 V.E..... .......D 0,1829-1080,2045} getView: 位置 9復用了位置2的view aaaaaaaaaa---------- getView: position = 8,convertView = android.widget.LinearLayout{4303d378 V.E..... ........ 0,1830-1080,2046} bbbbbbbbbb---------- getView: position = 8,convertView = android.widget.LinearLayout{4303d378 V.E..... .......D 0,1830-1080,2046} getView: 位置 8復用了位置0的view aaaaaaaaaa---------- getView: position = 7,convertView = android.widget.LinearLayout{430474b8 V.E..... ........ 0,1825-1080,2041} bbbbbbbbbb---------- getView: position = 7,convertView = android.widget.LinearLayout{430474b8 V.E..... .......D 0,1825-1080,2041} getView: 位置 7復用了位置6的view aaaaaaaaaa---------- getView: position = 6,convertView = android.widget.LinearLayout{43048f88 V.E..... ........ 0,1824-1080,2040} bbbbbbbbbb---------- getView: position = 6,convertView = android.widget.LinearLayout{43048f88 V.E..... .......D 0,1824-1080,2040} aaaaaaaaaa---------- getView: position = 5,convertView = android.widget.LinearLayout{43043ee0 V.E..... ........ 0,1822-1080,2038} bbbbbbbbbb---------- getView: position = 5,convertView = android.widget.LinearLayout{43043ee0 V.E..... .......D 0,1822-1080,2038} getView: 位置 5復用了位置4的view aaaaaaaaaa---------- getView: position = 4,convertView = android.widget.LinearLayout{430459e8 V.E..... ........ 0,1823-1080,2039} bbbbbbbbbb---------- getView: position = 4,convertView = android.widget.LinearLayout{430459e8 V.E..... .......D 0,1823-1080,2039} aaaaaaaaaa---------- getView: position = 3,convertView = android.widget.LinearLayout{43042410 V.E..... ........ 0,1829-1080,2045} bbbbbbbbbb---------- getView: position = 3,convertView = android.widget.LinearLayout{43042410 V.E..... .......D 0,1829-1080,2045} aaaaaaaaaa---------- getView: position = 2,convertView = android.widget.LinearLayout{4304de70 V.E..... ........ 0,1826-1080,2042} bbbbbbbbbb---------- getView: position = 2,convertView = android.widget.LinearLayout{4304de70 V.E..... .......D 0,1826-1080,2042} aaaaaaaaaa---------- getView: position = 1,convertView = android.widget.LinearLayout{4303ee70 V.E..... ........ 0,1828-1080,2044} bbbbbbbbbb---------- getView: position = 1,convertView = android.widget.LinearLayout{4303ee70 V.E..... .......D 0,1828-1080,2044} aaaaaaaaaa---------- getView: position = 0,convertView = android.widget.LinearLayout{43040940 V.E..... ........ 0,1788-1080,2004} bbbbbbbbbb---------- getView: position = 0,convertView = android.widget.LinearLayout{43040940 V.E..... .......D 0,1788-1080,2004}
如上所述,到底誰復用了誰是隨機不定的,這個我們也沒有必要去關心。我們只要知道position是不變的就行了。
另外,除了打日志。可以選中某一個位置的checkbox,然后上下滑動,如果某個checkbox也莫名的選中了,那就說明這個位置的checkbox復用了之前選中的那個checkbox。
問題3:Adapter的notifyDataSetChanged()方法作了什么事情
notifyDataSetChanged,會重新走一遍可見的position的getView方法。
問題4:復用出現的場景
1.if-else的坑:在Adapter中,如果綁定View的數據的時候如果有if判斷,往往很多人忘記了加else,這是大多數復用問題出現的根源之一。在一般情況下else不寫沒有邏輯錯誤,但是在ListView復
用的情況下如果不寫錯誤就會帶來錯亂的麻煩。
實際場景:
比如每個item可能有或沒有圖片picarrList,之前我只加了if判斷,如果有圖片就顯示。但后來上下一滑動之后發現沒有圖片的item竟然也顯示了其它了item的圖片,于是追根溯源發現是這里的問題。
2.checkbox等的復用問題:果如下圖,是一個簡單的CheckBox列表
第1頁剛好0-8索引,我將0索引處的checkbox設置為選中狀態,然后向下滑動,發現下一個出現的checkbox(索引為10,不是9,也不一定就是10,而是索引0完全消失之后第一個出現的item)竟然也選中了。
百度了一下,可以用Map<Interger,Boolean>來記錄對應position的checkbox的選中狀態。而且網上
的這個Map是事先就是預訂好大小的了,但實際中Map的大小是確定的。
細節1):Map<Interger,Boolean>來記錄對應position的checkbox的選中狀態,怎么初始化?
--1-- 可以先在成員或者構造方法里實例化Map對象
Map<Integer,Boolean> isTitleCheckBoxSelected = new HashMap();
--2-- 在getView方法里初始化Map對象,默認checkbox都是未選中狀態
if(!isTitleCheckBoxSelected.containsKey(position)){ Log.i(TAG, "bindData: init checkbox " + position); isTitleCheckBoxSelected.put(position,false); //如果啟動了全選,則新出現的view也要選中。 if(isSelectedAllStarted){ isTitleCheckBoxSelected.put(position,true); } }
上面的這段代碼其實是非常妙的,通過contains判斷,保證了初始化。如果后面操作了map,
也不會影響這段代碼對map的初始化。
map這種數據結構,由于key是唯一,可以做去重操作。這一點List則不可直接做到。
細節2):響應checkbox的OnCheckedChangeListener事件,將改變后的狀態保存到map中。
header_checkbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if(isCheckedByCode) return; isTitleCheckBoxSelected.put(position, !isTitleCheckBoxSelected.get(position)); } });
在onCheckedChanged方法里將對應position的checkbox的狀態反轉。
細節3):將map中的對應position的狀態值賦值給當前的checkbox
但是有個問題,checkbox的setChecked方法,看其源碼,會走OnCheckedChangeListener的回調
而此時,setChecked方法我只想設置View的狀態,并不想走它的回調方法。下面有2種方法可以解決這個問題
方法1:在setChecked方法的前后用一個變量夾住,在回調方法里通過這個變量判斷回調是不是在代碼
里通過setChecked觸發,如果是setChecked觸發的,則不執行map的取反的操作。
isCheckedByCode = true; header_checkbox.setChecked(isTitleCheckBoxSelected.get(position)); isCheckedByCode = false;
這種方法多申請了個變量,耦合度比較高。
方法2:在setChecked方法之前將checkbox的監聽設置為null,在setChecked方法之后設置真正的監聽。
除了checkbox,其它的一些view,也可以通過以上的方法來解決復用的問題。解決復用要遵循一個原則:MV分離,在view一些事件監聽里,一般情況下改變記錄狀態的Map值之后,切記立馬就將值設置給View,而應該通過notifyDatasetChanged()方法將狀態更新到view上。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。