Givemepass's Android 惡補筆記

如何在ListView中使用Holder pattern來重用view

| Comments

在ptt上面看到很多人問相同問題, 把我的經驗分享出來。
http://www.ptt.cc/bbs/AndroidDev/M.1413996267.A.CC8.html

BaseAdapter的特性就是可以重用View來節省資源,

ListView的重用結構會長這樣,

這張圖可以很清楚看到 item 9 一開始在recycle pool,

當使用者手滑動, item 1被滑出去item 9就進來listview可視範圍

item 1進入recycle pool, 照這個邏輯,

如果使用者繼續往上滑, 那麼item 2, 3...就會進入回收區,

item 9, 1, 2 ...就會出現在畫面上。

這代表什麼意思? 代表著就算你資料有一百筆, 一千筆,

畫面呈現的也只有9個item, 這邊代表著view也只生成9個,

就不會佔用太多的資源。

那我怎麼知道他是第幾筆資料?

這個不用擔心 BaseAdapter幫你處理好這件事情了。

只要你資料順序塞好, 不管你是丟在怎樣的資料結構,

不管是ArrayList, Array, HashMap...等等。

只要你資料設定好, 就可以開始使用這個有趣的設計了。

程式怎麼做呢?

一開始宣告一個MyAdapter繼承BaseAdapter

public class MyAdapter extends BaseAdapter {

        @Override
        public int getCount() {
                // TODO Auto-generated method stub

                return 0;
        }

        @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 arg0, View arg1, ViewGroup arg2) {
                // TODO Auto-generated method stub

                return null;
        }

}

上面你會看到四個方法, 基本上只要處理兩個方法

getCount跟getView即可,

剩下的兩個方法暫時不會用到, 有興趣的可以研究看看。

getCount就是跟adapter說你有幾筆資料要呈現,

假設現在你有100筆資料, 塞在arraylist內,

那麼我們來模擬一下

private ArrayList<Integer> mList;

public MyAdapter(){
   mList = new ArrayList<Integer>();
   for(int i = 0; i < 100; i++){
       mList.add(i);
   }
}

首先在建構子上建立一個arraylist並且塞值到這個陣列內,

接著把資料結構的長度塞給getCount方法,

讓listview知道你的資料有多長。

    @Override
    public int getCount() {
            // TODO Auto-generated method stub
            return mList.size();
    }

接著到重頭戲getView

這個方法就是listView會不斷的來存取, 是一個callback function,

而不是由你自己去存取,

在看一次這個方法 我把參數改成英文名稱比較好認

 @Override
 public View getView(int position, View convertView, ViewGroup parent) {
    // TODO Auto-generated method stub

    return null;
 }

第一個是我們的item到哪一個位置?

第二個是我們這個item所使用的view

第三個是我們item的parent

一開始會先把目前的view抓出來 判斷他是不是被初始化過

如果有被初始化過 代表著他之前就被使用過了

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
            View view = convertView;
            if(null == view){

            } else{

            }
            return view;
    }

這樣一來我們就可以把這個實體化過的view傳回去給listview了。

接著如果他是空的 則對他進行初始化

要初始化之前 先取得inflate

回到建構子補上

        private LayoutInflater mLayoutInflater;

        public MyAdapter(Context mContext){
                mLayoutInflater = LayoutInflater.from(mContext);
                ...
        }

再回到getView

那個layout就是我們item view所裝的xml布局

透過LayoutInflater可以實體化成為一個view的物件

這樣一來就可以使用程式做很多靈活的操作了。

那layout裡面長怎樣呢?

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/item_text"
    />

</RelativeLayout>

因為是範例所以寫得很簡單 只放入一個textview

接著就要把這個textview存在holder裡面

什麼是holder?

就是幫我們記住item內所有元件的內容

先宣告一個類別來存放

    class Holder{
            TextView itemText;
    }

因為我們item layout很簡單 所以這樣就完成一個宣告

接著回到getView

    Holder holder;
    if(null == view){
        view = mLayoutInflater.inflate(R.layout.listview_item, null);
        holder = new Holder();
        holder.itemText = (TextView) view.findViewById(R.id.item_text);
        view.setTag(holder);
    }

由上面可以看到 當view是空的時候

就把holder new出來 然後把itemText裝進去

在透過view設定tag 裝進holder

那麼下次view不是空的條件下 就可以直接把holder拿出來用

就完成了reuse了功效了

    else{
        holder = (Holder) view.getTag();
    }

這個adapter基本型態已經完成,

然後把資料結構內的資料塞進每一個item

 holder.itemText.setText(mList.get(position) + "");

就會出現這樣的畫面

現在要來說明怎麼透過改變資料 來更新view

假設我們要改變某一列

假設是第2筆資料出現的地方背景就顯示紅色

那麼只要在retun view;之前加入

if(position == 2){
     view.setBackgroundColor(Color.RED);
}

就會出現第二列是紅色的

但是很奇怪 你往上滑動 居然第24列也是紅色的

這個就是reuse要注意的地方

因為listview就是把滑出去的item拿過來重用

所以那個重用的item背景還是紅色的

因此每一個item一開始都要先初始化

view.setBackgroundColor(Color.WHITE);
if(position == 2){
     view.setBackgroundColor(Color.RED);
}

這樣就正常了

至於你的問題 想要讓一個Button按下去 某一個值進行變化

其實很簡單 只要開一個方法

public void setRowColor(int pos, int value){
   mList.set(pos, value);
   notifyDataSetChanged();
}

notifyDataSetChanged是用來刷新listview的畫面

也就是會再去讀取一圈getview

假設剛剛變化背景的判斷式改成

if(mList.get(position) == 200){
   view.setBackgroundColor(Color.RED);
}

外面設定一個button 按下的時候 將arraylist的值改變

那麼你就會看到listview有一列變紅色的

這就是用值去改變view

而不是用view去修正值

程式碼

http://uploadingit.com/file/ffokgjkq0kjer5tq/TestAndroid.zip

Comments

comments powered by Disqus