BaseAdapter——convertView回收機制與動態控制元件響應
前言:對於listView的BaseAdapter的派生,難度比較大。最難理解的莫過於getView(int position, View convertView, ViewGroup parent)這個函式是如何產生每條記錄的,有些部落格中利用holderView,有些部落格卻沒有用,種種方法之間有什麼異同,今天我們就來揭開這個繪製ITEM機制的面紗。
本篇藉助《PullToRefresh使用詳解(二)---重寫BaseAdapter實現複雜XML下拉重新整理》的例子。所以本篇對於程式碼的講解就比較粗略,如果有讀者對於如何重寫BaseAdapter不太熟悉的話,請先移步看看這篇文章,然後再回來這裡,相信會有不一樣的收穫。
一、ConvertView回收機制
工作原理:
1、ListView 針對List中每個item,要求 adapter “給我一個檢視” (getView)。
2、一個新的檢視被返回並顯示
如果我們有上億個專案要顯示怎麼辦?為每個專案建立一個新檢視?NO!這不可能!
實際上Android為你快取了檢視。Android中有個叫做Recycler的構件,下圖是他的工作原理:
如果你有10億個專案(item),其中只有可見的專案存在記憶體中,其他的在Recycler中。
ListView先請求一個type1檢視(getView)然後請求其他可見的專案。convertView在getView中是空(null)的。
當item1滾出螢幕,並且一個新的專案從螢幕低端上來時,ListView再請求一個type1檢視。convertView此時不是空值了,它的值是item1。你只需設定新的資料然後返回convertView,不必重新建立一個檢視。 以上摘自《
也就是說:
1、android的listView在初始化的時候,如上面這個列表,整個螢幕只能放下7個item,那麼listView在初始化時,就會只建立7個view,對於這些view也就是引數中的convertView。
2、那問題來了,當繼續網上滑動,item1消失了,而item8出來了。那系統還是為item8重新建立一個新的convertView嗎?另一個問題,item1的convertView去哪了?(銷燬回收資源,還是重新利用?)如果你是系統設計者,你會怎麼做?大家想想,如果為每個要顯示的item都建立新convertView是不是太浪費了,況且對於item1的convertView已經沒用了,我們何不把它拿來給item8用。對!系統就是這樣做的!這就是convertView的回收機制。就是將那些不再被用的ITEM的convertView重新給即將顯示的ITEM使用的機制!
二、例子
先給大家看一下單個ITEM的佈局圖片,對於具體佈局程式碼,看原始碼吧。
對於JAVA原始碼,我們先看這種方式寫的convertView的生成方法。
package com.example.try_pulltorefresh_map;
/**
* 完成了從TXT文字中提取,並向下重新整理
* blog:http://blog.csdn.net/harvic880925
* @author harvic
* @date 2014-5-8
*
*/
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import org.json.JSONArray;
import com.handmark.pulltorefresh.library.PullToRefreshBase;
import com.handmark.pulltorefresh.library.PullToRefreshListView;
import com.handmark.pulltorefresh.library.PullToRefreshBase.Mode;
import com.handmark.pulltorefresh.library.PullToRefreshBase.OnRefreshListener;
import android.os.Bundle;
import android.app.ListActivity;
import android.content.Context;
import android.graphics.Color;
import android.text.format.DateUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
public class MainActivity extends ListActivity {
private ArrayList<HashMap<String, Object>> listItem = new ArrayList<HashMap<String, Object>>();
private PullToRefreshListView mPullRefreshListView;
MyAdapter adapter=null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mPullRefreshListView = (PullToRefreshListView) findViewById(R.id.pull_refresh_list);
//設定下拉監聽函式
mPullRefreshListView.setOnRefreshListener(new OnRefreshListener<ListView>() {
@Override
public void onRefresh(PullToRefreshBase<ListView> refreshView) {
String label = DateUtils.formatDateTime(getApplicationContext(), System.currentTimeMillis(),
DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_ALL);
// Update the LastUpdatedLabel
refreshView.getLoadingLayoutProxy().setLastUpdatedLabel(label);
// Do work to refresh the list here.
}
});
mPullRefreshListView.setMode(Mode.PULL_FROM_END);//設定底部下拉重新整理模式
listItem=getData();//獲取LIST資料
adapter = new MyAdapter(this);
//設定介面卡
ListView actualListView = mPullRefreshListView.getRefreshableView();
actualListView.setAdapter(adapter);
}
private ArrayList<HashMap<String, Object>> getData() {
ArrayList<HashMap<String, Object>> list = new ArrayList<HashMap<String, Object>>();
HashMap<String, Object> map = new HashMap<String, Object>();
InputStream inputStream;
try {
inputStream=this.getAssets().open("my_home_friends.txt");
String json=readTextFile(inputStream);
JSONArray array = new JSONArray(json);
for (int i = 0; i < array.length(); i++) {
map = new HashMap<String, Object>();
map.put("name", array.getJSONObject(i).getString("name"));
map.put("info", array.getJSONObject(i).getString("info"));
map.put("img",array.getJSONObject(i).getString("photo"));
list.add(map);
}
return list;
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
return list;
}
public final class ViewHolder{
public TextView name;
public TextView info;
public TextView attentntion;
}
public class MyAdapter extends BaseAdapter{
private LayoutInflater mInflater;
public MyAdapter(Context context){
this.mInflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return listItem.size();
}
@Override
public Object getItem(int arg0) {
// TODO Auto-generated method stub
return null;
}
@Override
public long getItemId(int arg0) {
// TODO Auto-generated method stub
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
System.out.println("position:"+position+" convertView:"+convertView);
ViewHolder holder = null;
holder=new ViewHolder();
convertView = mInflater.inflate(R.layout.item, null);
holder.name = (TextView)convertView.findViewById(R.id.name);
holder.info = (TextView)convertView.findViewById(R.id.info);
holder.attentntion=(TextView)convertView.findViewById(R.id.attention);
holder.name.setText((String)listItem.get(position).get("name"));
holder.info.setText((String)listItem.get(position).get("info"));
final TextView attention=holder.attentntion;
holder.attentntion.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
attention.setTextColor(Color.RED);
}
});
convertView.setTag(holder);
return convertView;
}
}
////工具類
/**
*
* @param inputStream
* @return
*/
public String readTextFile(InputStream inputStream) {
String readedStr = "";
BufferedReader br;
try {
br = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
String tmp;
while ((tmp = br.readLine()) != null) {
readedStr += tmp;
}
br.close();
inputStream.close();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return readedStr;
}
}
以上程式碼凡是懂如何派生自BaseAdapter的應該都可以看懂,這裡就不再多講,只看核心程式碼,摘錄如下:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
System.out.println("position:"+position+" convertView:"+convertView);
ViewHolder holder = null;
holder=new ViewHolder();
convertView = mInflater.inflate(R.layout.item, null);
holder.name = (TextView)convertView.findViewById(R.id.name);
holder.info = (TextView)convertView.findViewById(R.id.info);
holder.attentntion=(TextView)convertView.findViewById(R.id.attention);
holder.name.setText((String)listItem.get(position).get("name"));
holder.info.setText((String)listItem.get(position).get("info"));
final TextView attention=holder.attentntion;
holder.attentntion.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
attention.setTextColor(Color.RED);
}
});
return convertView;
}
這裡利用system.out.println對convertView進行捕捉,執行如果如下:
手機初始化是這樣子的:
根據上面我們講的理論,在初始化時,整個螢幕能放下三個ITEM,所以會建立三個全新的convertView。當我往下拉一個ITEM,出現第四個ITEM的時候,就會回收第一個ITEM的convertView給第四個。捕捉結果如下:
清楚的看到,前四個convertView為NULL,當第五個ITEM出現時,此時由於第一個ITEM肯定已經滾出螢幕,所以將其重新傳給即將出現的item5使用。我們上面說的第四個ITEM出現的時候就應該不再建立新convertView了,我想android開發者在考慮多建立一個ITEM的目的在於更安全吧。
回到上面的程式碼,好像看著程式碼沒有任何問題,我在裡面寫了個clickListener,當點選“關注”的時候,字型會變紅,試一下。
點選“關注” 下拉後再拉回來
問題出現了:當拉回來的時候,“關注”不再紅了!!!!!!為什麼????
問題出在程式碼上:
holder=new ViewHolder();
convertView = mInflater.inflate(R.layout.item, null);
holder.name = (TextView)convertView.findViewById(R.id.name);
holder.info = (TextView)convertView.findViewById(R.id.info);
holder.attentntion=(TextView)convertView.findViewById(R.id.attention);
每次執行getView獲取當前ITEM時,都會重新new 一個viewHolder與R.layout.item繫結,也就是說,每次都會產生一個新佈局賦值給convertView讓其顯示。而我們上面講了,android會將回收過來的convertView返回給即將顯示的getView使用,以節約資源。而我們這裡卻沒有領情,每次都重新建立一個佈局賦給convertView,由於每次都建立一個新佈局,所以當ITEM1被重新拉回來顯示的時候,由於是重新建立的佈局,當然是初始狀態。“關注”當然也就是黑色的了。
改進:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
System.out.println("position:"+position+" convertView:"+convertView);
ViewHolder holder = null;
if (convertView == null) {
holder=new ViewHolder();
convertView = mInflater.inflate(R.layout.item, null);
holder.name = (TextView)convertView.findViewById(R.id.name);
holder.info = (TextView)convertView.findViewById(R.id.info);
holder.attentntion=(TextView)convertView.findViewById(R.id.attention);
convertView.setTag(holder);
}else {
holder = (ViewHolder)convertView.getTag();
}
holder.name.setText((String)listItem.get(position).get("name"));
holder.info.setText((String)listItem.get(position).get("info"));
final TextView attention=holder.attentntion;
holder.attentntion.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
attention.setTextColor(Color.RED);
}
});
return convertView;
}
不同的部分在這:
ViewHolder holder = null;
if (convertView == null) {
holder=new ViewHolder();
convertView = mInflater.inflate(R.layout.item, null);
holder.name = (TextView)convertView.findViewById(R.id.name);
holder.info = (TextView)convertView.findViewById(R.id.info);
holder.attentntion=(TextView)convertView.findViewById(R.id.attention);
convertView.setTag(holder);
}else {
holder = (ViewHolder)convertView.getTag();
}
當convertView為空,即初始化建立時,我們就將生成的佈局利用setTag()儲存在convertView中,當convertView利用回收機制回收過來讓我們再次使用時,我們通過getTag()將儲存的佈局取出來,重新將佈局裡的各個控制元件重新賦值就可以了。這裡就利用了android-listView的回收機制。
再看"關注"的點選事件執行的怎樣:
點選 拉下去再拉回來 再往下拉
看第二張圖,當拉下去再拉回來的時候,一切正常,但當我們再往下拉(第三張圖),問題又出現了,明明沒有點P-5,為什麼關注反而是紅色的!!!!!!
這是因為,P-5用的是P-1回收來的convertView!!!而P-1的convertView的佈局裡“關注”是紅色的。所以只要回收機制在,我們就沒有辦法改變從P-1回收來的convertView裡的圖片佈局,除非人為的將其重置!
理解了這個問題以後,我們想想解決辦法。
首先申請一個arrayList attentionArr變數,儲存使用者點選“關注”的ITEM的position,然後在繪製當前ITEM時,根據這個position是否在attentionArr裡來判斷是不是將“關注”重新變紅。
程式碼如下:
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
System.out.println("position:"+position+" convertView:"+convertView);
ViewHolder holder = null;
if (convertView == null) {
holder=new ViewHolder();
convertView = mInflater.inflate(R.layout.item, null);
holder.name = (TextView)convertView.findViewById(R.id.name);
holder.info = (TextView)convertView.findViewById(R.id.info);
holder.attentntion=(TextView)convertView.findViewById(R.id.attention);
convertView.setTag(holder);
}else {
holder = (ViewHolder)convertView.getTag();
}
holder.name.setText((String)listItem.get(position).get("name"));
holder.info.setText((String)listItem.get(position).get("info"));
final TextView attention=holder.attentntion;
//根據當前position判斷,重新制做樣式
if (attentionArr.contains(position)) {
attention.setTextColor(Color.RED);
}else {
attention.setTextColor(Color.BLACK);
}
holder.attentntion.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
attention.setTextColor(Color.RED);
attentionArr.add(position);//在點選時將position加入其中
}
});
return convertView;
}
理解程式碼難度不大,首先在OnClickListener時,將position加入到attentionArr陣列中,然後在getView裡,判斷當前position是不是使用者點選過的,即是否包含在attentionArr陣列中,如果是,則將“關注”置為紅色,否則置為初始色,黑色。原始碼說明:根據本文的順序,分為三個原始碼,第一個即全部重新建立convertView版,第二個是利於回收機制,將holderView保存於convertView中,然後再取出來的那版,最後一個,是基於第二個的基礎上修改的,也是本篇的最終版。
請大家尊重原創者版權,轉載請標明出處:http://blog.csdn.net/harvic880925/article/details/25335957 不勝感激!