1. 程式人生 > >_065_Android_BaseAdapter的convertView回收機制與動態控制元件響應

_065_Android_BaseAdapter的convertView回收機制與動態控制元件響應

轉自http://blog.csdn.net/harvic880925/article/details/25335957,感謝作者的無私分享。 

對於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,不必重新建立一個檢視。   以上摘自《

ListView中getView的原理+如何在ListView中放置多個item

也就是說:

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被重新拉回來顯示的時候,由於是重新建立的佈局,當然是初始狀態。“關注”當然也就是黑色的了。
改進:

[java] view plain copy

@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陣列中,如果是,則將“關注”置為紅色,否則置為初始色,黑色。

 

原始碼地址:http://download.csdn.net/detail/harvic880925/7320141 不要分,僅供分享

原始碼說明:根據本文的順序,分為三個原始碼,第一個即全部重新建立convertView版,第二個是利於回收機制,將holderView保存於convertView中,然後再取出來的那版,最後一個,是基於第二個的基礎上修改的,也是本篇的最終版。

 

請大家尊重原創者版權,轉載請標明出處:http://blog.csdn.net/harvic880925/article/details/25335957 不勝感激!