編輯:關於Android編程
前言:
為了使ListView性能更優,最普遍的方法就是添加一個ViewHolder靜態類。
雖然性能有很大的提高,但是同樣也伴隨著Item控件內容顯示重復或錯亂的情況。
分析並解決如下兩個問題
一、控件數據未初始化而導致的顯示錯誤。
二、網絡異步加載導致出現顯示錯誤、重復。
如下我們來簡單分析一下ListView的緩存機制。我們整篇文章均以下圖的模型來舉例說明。
(圖片轉至http://www.cnblogs.com/xiaowenji/archive/2010/12/08/1900579.html)
上圖中的過程分析:
1.在getView()獲取每行的Item並向下滑動的過程中,如果Item1已經完全滑出屏幕,且緩存中沒有Item1對應的View,則將其put進緩存中。
2.將要滑入的Item8會先判斷緩存中是否有可用的Item,如果有則直接將緩存中對應的View拿過來復用。
3.ListView顯示剛剛滑入的Item8,並將ListView中的各個Item都執行刷新(getView())操作。
那麼問題來了...
一、控件數據未初始化而導致的顯示錯誤
執行Item8的getView()過程中由於復用的是Item1,那麼Item8的所有初始值就是Item1的值,如果在Item8中沒有對Item中任何控件重新賦值的話,那麼顯示的內容會和跟Item1一模一樣。
實例代碼:
public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder; if (convertView == null){ viewHolder = new ViewHolder(); convertView = layoutInflater.inflate(R.layout.listview,parent,false); viewHolder.tvTitle = (TextView)convertView.findViewById(R.id.tv_title); convertView.setTag(viewHolder); }else{ viewHolder = (ViewHolder)convertView.getTag(); } //Item7以上的所有Item都不賦值 if(position <= 7){ viewHolder.tvTitle.setText(title + String.valueOf(position)); } return convertView; }
運行效果如下
我們發現Item7之後由於未重新賦值,所以都是復用的ListView緩存機制中的View。
問題的解決:
這個問題比較好解決,就是把每項Item所有控件都賦值,這樣就把緩存中的初始數據給覆蓋掉。
代碼如下
public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder; if (convertView == null){ viewHolder = new ViewHolder(); convertView = layoutInflater.inflate(R.layout.listview,parent,false); viewHolder.tvTitle = (TextView)convertView.findViewById(R.id.tv_title); convertView.setTag(viewHolder); }else{ viewHolder = (ViewHolder)convertView.getTag(); } //if(position <= 7){ viewHolder.tvTitle.setText(title + String.valueOf(position)); //} return convertView; }
效果圖:
問題解決。
二、網絡異步加載導致出現顯示錯誤、重復。
舉個例子,比如Item有點贊功能,由於比較懶,未做實例,所以來來來...大家跟我一起在腦海中想象吧。
我們假設用戶點了Item1的贊按鈕(此時開始請求服務器...),當請求服務器的過程中,你突然向下滑動。
如上圖所示,Item1被放入緩存,而Item8復用Item1的View。就在這時Item1的點贊請求剛剛請求成功,並執行顯示贊狀態的操作,此時Item8由於是復用的Item1的View,所以Item8的贊按鈕就會被莫名其妙的點亮。然後就蒙圈了,就開始尋思,誰?誰啊?你誰?圖片的加載錯誤也是這個原因。(圖片的解決方案http://www.trinea.cn/android/android-listview-display-error-image-when-scroll/)
3個解決方案:
1.不使用 ViewHolder就不存在這種問題,每次有Item滑入會重新創建控件,但這你能用嗎?好吧,願意用就用吧;
2.HashMap來標記對應的View(HashMap
3.Tag標記,最終方案。
下面我們來講解一下通過Tag標記來解決上述問題。
先貼一下純手打的代碼
public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder; if (convertView == null){ viewHolder = new ViewHolder(); convertView = layoutInflater.inflate(R.layout.listview,parent,false); viewHolder.btnZ = (Button)convertView.findViewById(R.id.button); convertView.setTag(viewHolder); }else{ viewHolder = (ViewHolder)convertView.getTag(); } viewHolder.btnZ.setTag(position); viewHolder.btnZ.setText(踩); viewHolder.btnZ.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if(//訪問服務器成功){ if ((int) viewHolder.btnZ.getTag() == position) { //異步顯示 viewHolder.btnZ.setText(贊); 的操作 } } } }); return convertView; }
針對上面的代碼,講一下Tag在ListView中的所起的作用。
用戶在Item1點了贊按鈕後,此時Item1的Button進入請求服務器的過程中,用戶向下滑,Item1滑出,將View復用給Item8並讓其顯示之後,此時Item1和Item8同時控制著同一個View,只不過Item8在顯示的時候已經把復用的View重新按照它的數據覆蓋了一下並把View上viewHolder.btnZ的tag重新標記成它的標記,而假設此時恰好Item1的請求才剛剛成功並要刷新了一下自己的控件,然而根據上面的代碼...Item如果要顯示自己的加載成功之後的狀態,需要判斷一下tag和position,此時Item1執行viewHolder.btnZ.getTag()方法,發現tag已經是7了(Item8的),而Item1自己的position是0。所以,被無情的拒絕在了門外~
所以我們用這種對比的方法就可以很輕松的來避免Item顯示出錯的問題。
總結一下:
在出現異步加載的過程中,滑動ListView,Item被復用且前一個Item剛剛異步加載成功的情況下。
getTag()獲取的是當前正在顯示的”正確的Item標記,而position則很可能是復用View之前的那個Item。
如果了解上述原理,相信不管是圖片還是button神馬的都可以避免出現顯示錯誤的問題啦。
小米手環需要安卓4.4及以上系統,於是很多朋友購買之後進行手機系統升級,但是卻發現小米手環充不了電。遇到這種情況要怎麼辦呢?那麼小米手環充不了電怎麼辦呢?小
前言前幾篇文章中,筆者對View的三大工作流程進行了詳細分析,而這篇文章則詳細講述與三大工作流程密切相關的兩個方法,分別是requestLayout和invalidate
在移動平台中設備與用戶的交互必須通過事件處理完成。用戶輸入被封裝為事件,Cocos2d-x游戲引擎能夠接收並處理這些事件,包括觸摸事件、鍵盤事件、鼠標事件、加速度事件和自