Android之BaseAdapter—convertView回收机制与动态控件响应
前言:對(duì)于listView的BaseAdapter的派生,難度比較大。最難理解的莫過(guò)于getView(int position, View convertView, ViewGroup parent)這個(gè)函數(shù)是如何產(chǎn)生每條記錄的,有些博客中利用holderView,有些博客卻沒(méi)有用,種種方法之間有什么異同,今天我們就來(lái)揭開(kāi)這個(gè)繪制ITEM機(jī)制的面紗。
本篇借助《PullToRefresh使用詳解(二)---重寫(xiě)B(tài)aseAdapter實(shí)現(xiàn)復(fù)雜XML下拉刷新》的例子。所以本篇對(duì)于代碼的講解就比較粗略,如果有讀者對(duì)于如何重寫(xiě)B(tài)aseAdapter不太熟悉的話,請(qǐng)先移步看看這篇文章,然后再回來(lái)這里,相信會(huì)有不一樣的收獲。
一、ConvertView回收機(jī)制
工作原理:
1、ListView 針對(duì)List中每個(gè)item,要求 adapter “給我一個(gè)視圖” (getView)。
2、一個(gè)新的視圖被返回并顯示
如果我們有上億個(gè)項(xiàng)目要顯示怎么辦?為每個(gè)項(xiàng)目創(chuàng)建一個(gè)新視圖?NO!這不可能!
實(shí)際上Android為你緩存了視圖。Android中有個(gè)叫做Recycler的構(gòu)件,下圖是他的工作原理:
如果你有10億個(gè)項(xiàng)目(item),其中只有可見(jiàn)的項(xiàng)目存在內(nèi)存中,其他的在Recycler中。
ListView先請(qǐng)求一個(gè)type1視圖(getView)然后請(qǐng)求其他可見(jiàn)的項(xiàng)目。convertView在getView中是空(null)的。
當(dāng)item1滾出屏幕,并且一個(gè)新的項(xiàng)目從屏幕低端上來(lái)時(shí),ListView再請(qǐng)求一個(gè)type1視圖。convertView此時(shí)不是空值了,它的值是item1。你只需設(shè)定新的數(shù)據(jù)然后返回convertView,不必重新創(chuàng)建一個(gè)視圖。?? 以上摘自《ListView中g(shù)etView的原理+如何在ListView中放置多個(gè)item》
也就是說(shuō):
1、android的listView在初始化的時(shí)候,如上面這個(gè)列表,整個(gè)屏幕只能放下7個(gè)item,那么listView在初始化時(shí),就會(huì)只創(chuàng)建7個(gè)view,對(duì)于這些view也就是參數(shù)中的convertView。
2、那問(wèn)題來(lái)了,當(dāng)繼續(xù)網(wǎng)上滑動(dòng),item1消失了,而item8出來(lái)了。那系統(tǒng)還是為item8重新創(chuàng)建一個(gè)新的convertView嗎?另一個(gè)問(wèn)題,item1的convertView去哪了?(銷(xiāo)毀回收資源,還是重新利用?)如果你是系統(tǒng)設(shè)計(jì)者,你會(huì)怎么做?大家想想,如果為每個(gè)要顯示的item都創(chuàng)建新convertView是不是太浪費(fèi)了,況且對(duì)于item1的convertView已經(jīng)沒(méi)用了,我們何不把它拿來(lái)給item8用。對(duì)!系統(tǒng)就是這樣做的!這就是convertView的回收機(jī)制。就是將那些不再被用的ITEM的convertView重新給即將顯示的ITEM使用的機(jī)制!
二、例子
先給大家看一下單個(gè)ITEM的布局圖片,對(duì)于具體布局代碼,看源碼吧。
對(duì)于JAVA源碼,我們先看這種方式寫(xiě)的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;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mPullRefreshListView = (PullToRefreshListView) findViewById(R.id.pull_refresh_list);//設(shè)定下拉監(jiān)聽(tīng)函數(shù)mPullRefreshListView.setOnRefreshListener(new OnRefreshListener<ListView>() {@Overridepublic 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 LastUpdatedLabelrefreshView.getLoadingLayoutProxy().setLastUpdatedLabel(label);// Do work to refresh the list here.}});mPullRefreshListView.setMode(Mode.PULL_FROM_END);//設(shè)置底部下拉刷新模式listItem=getData();//獲取LIST數(shù)據(jù)adapter = new MyAdapter(this);//設(shè)置適配器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 exceptione.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);}@Overridepublic int getCount() {// TODO Auto-generated method stubreturn listItem.size();}@Overridepublic Object getItem(int arg0) {// TODO Auto-generated method stubreturn null;}@Overridepublic long getItemId(int arg0) {// TODO Auto-generated method stubreturn 0;}@Overridepublic 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() { @Overridepublic void onClick(View v) {// TODO Auto-generated method stubattention.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的應(yīng)該都可以看懂,這里就不再多講,只看核心代碼,摘錄如下:
@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() { @Overridepublic void onClick(View v) {// TODO Auto-generated method stubattention.setTextColor(Color.RED);}}); return convertView; }
這里利用system.out.println對(duì)convertView進(jìn)行捕捉,運(yùn)行如果如下:
手機(jī)初始化是這樣子的:
根據(jù)上面我們講的理論,在初始化時(shí),整個(gè)屏幕能放下三個(gè)ITEM,所以會(huì)創(chuàng)建三個(gè)全新的convertView。當(dāng)我往下拉一個(gè)ITEM,出現(xiàn)第四個(gè)ITEM的時(shí)候,就會(huì)回收第一個(gè)ITEM的convertView給第四個(gè)。捕捉結(jié)果如下:
清楚的看到,前四個(gè)convertView為NULL,當(dāng)?shù)谖鍌€(gè)ITEM出現(xiàn)時(shí),此時(shí)由于第一個(gè)ITEM肯定已經(jīng)滾出屏幕,所以將其重新傳給即將出現(xiàn)的item5使用。我們上面說(shuō)的第四個(gè)ITEM出現(xiàn)的時(shí)候就應(yīng)該不再創(chuàng)建新convertView了,我想android開(kāi)發(fā)者在考慮多創(chuàng)建一個(gè)ITEM的目的在于更安全吧。
回到上面的代碼,好像看著代碼沒(méi)有任何問(wèn)題,我在里面寫(xiě)了個(gè)clickListener,當(dāng)點(diǎn)擊“關(guān)注”的時(shí)候,字體會(huì)變紅,試一下。
??????????? 點(diǎn)擊“關(guān)注”???????????????????? 下拉后再拉回來(lái)
問(wèn)題出現(xiàn)了:當(dāng)拉回來(lái)的時(shí)候,“關(guān)注”不再紅了!!!!!!為什么????
問(wèn)題出在代碼上:
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); 每次運(yùn)行g(shù)etView獲取當(dāng)前ITEM時(shí),都會(huì)重新new 一個(gè)viewHolder與R.layout.item綁定,也就是說(shuō),每次都會(huì)產(chǎn)生一個(gè)新布局賦值給convertView讓其顯示。而我們上面講了,android會(huì)將回收過(guò)來(lái)的convertView返回給即將顯示的getView使用,以節(jié)約資源。而我們這里卻沒(méi)有領(lǐng)情,每次都重新創(chuàng)建一個(gè)布局賦給convertView,由于每次都創(chuàng)建一個(gè)新布局,所以當(dāng)ITEM1被重新拉回來(lái)顯示的時(shí)候,由于是重新創(chuàng)建的布局,當(dāng)然是初始狀態(tài)。“關(guān)注”當(dāng)然也就是黑色的了。
改進(jìn)
@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() { @Overridepublic void onClick(View v) {// TODO Auto-generated method stubattention.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(); }
當(dāng)convertView為空,即初始化創(chuàng)建時(shí),我們就將生成的布局利用setTag()保存在convertView中,當(dāng)convertView利用回收機(jī)制回收過(guò)來(lái)讓我們?cè)俅问褂脮r(shí),我們通過(guò)getTag()將保存的布局取出來(lái),重新將布局里的各個(gè)控件重新賦值就可以了。這里就利用了android-listView的回收機(jī)制。
再看"關(guān)注"的點(diǎn)擊事件運(yùn)行的怎樣:
看第二張圖,當(dāng)拉下去再拉回來(lái)的時(shí)候,一切正常,但當(dāng)我們?cè)偻吕?#xff08;第三張圖),問(wèn)題又出現(xiàn)了,明明沒(méi)有點(diǎn)P-5,為什么關(guān)注反而是紅色的!!!!!!
這是因?yàn)?#xff0c;P-5用的是P-1回收來(lái)的convertView!!!而P-1的convertView的布局里“關(guān)注”是紅色的。所以只要回收機(jī)制在,我們就沒(méi)有辦法改變從P-1回收來(lái)的convertView里的圖片布局,除非人為的將其重置!
理解了這個(gè)問(wèn)題以后,我們想想解決辦法。
首先申請(qǐng)一個(gè)arrayList ?attentionArr變量,保存用戶點(diǎn)擊“關(guān)注”的ITEM的position,然后在繪制當(dāng)前ITEM時(shí),根據(jù)這個(gè)position是否在attentionArr里來(lái)判斷是不是將“關(guān)注”重新變紅。
代碼如下:
@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;//根據(jù)當(dāng)前position判斷,重新制做樣式if (attentionArr.contains(position)) {attention.setTextColor(Color.RED);}else {attention.setTextColor(Color.BLACK);}holder.attentntion.setOnClickListener(new View.OnClickListener() { @Overridepublic void onClick(View v) {// TODO Auto-generated method stubattention.setTextColor(Color.RED);attentionArr.add(position);//在點(diǎn)擊時(shí)將position加入其中}});return convertView; }
理解代碼難度不大,首先在OnClickListener時(shí),將position加入到attentionArr數(shù)組中,然后在getView里,判斷當(dāng)前position是不是用戶點(diǎn)擊過(guò)的,即是否包含在attentionArr數(shù)組中,如果是,則將“關(guān)注”置為紅色,否則置為初始色,黑色。
總結(jié)
以上是生活随笔為你收集整理的Android之BaseAdapter—convertView回收机制与动态控件响应的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: git之you can‘t overwr
- 下一篇: Android之px 与 dp, sp换