2012-10-31 138 views
9

有點新手問題。我們爲什麼要初始化getView()中的ViewHolder?爲什麼我們不能在構造函數中初始化它?ViewHolder - 良好做法

+1

http://www.youtube.com/watch?v=wDBM6wVEO70年底更新之前。看看這個視頻。應該回答你的問題。 – Raghunandan

回答

29

您將有多個ViewHolder對象存在。

A ListView其性質不會爲其每行創建新的View實例。這是如此,如果你有一百萬件事情的ListView,你不需要存儲一百萬件佈局信息。那麼你需要存儲什麼?只是在屏幕上的東西。然後,您可以重複使用這些視圖。這樣,您的ListView百萬個對象可能只有10個子視圖。

在您的自定義陣列適配器,你將有一個名爲getView()功能,看起來是這樣的:

public View getView(int position, View convertView, ViewGroup parent) { 
    //Here, position is the index in the list, the convertView is the view to be 
    //recycled (or created), and parent is the ListView itself. 

    //Grab the convertView as our row of the ListView 
    View row = convertView; 

    //If the row is null, it means that we aren't recycling anything - so we have 
    //to inflate the layout ourselves. 
    if(row == null) { 
      LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
      row = inflater.inflate(R.layout.list_item, parent, false); 
    } 

    //Now either row is the recycled view, or one that we've inflated. All that's left 
    //to do is set the data of the row. In this case, assume that the row is just a 
    //simple TextView 
    TextView textView = (TextView) row.findViewById(R.id.listItemTextView); 

    //Grab the item to be rendered. In this case, I'm just using a string, but 
    //you will use your underlying object type. 
    final String item = getItem(position); 

    textView.setText(item); 

    //and return the row 
    return row; 
} 

這是可行的,但需要一些時間,看看你是否可以在這裏發現的低效率。考慮一下上面哪段代碼會被重複調用。

問題是,我們一次又一次地打電話給row.findViewById,即使第一次查找它後,它永遠不會改變。如果你的列表中只有一個簡單的TextView,那麼它可能並不是那麼糟糕,如果你有一個複雜的佈局,或者你有多個你想要設置數據的視圖,你可能會失去一些時間來查找你的視圖,再次。

那麼我們如何解決這個問題呢?那麼,在我們查看之後將TextView存儲在某個地方是有意義的。所以我們引入一個名爲ViewHolder的類,它可以「保留」視圖。所以適配器的內部,引入一個內部類,像這樣:

private static class ViewHolder { 
    TextView textView; 
} 

這個類是私有的,因爲它只是一個適配器一個緩存機制,所以我們並不需要的參考它是靜態的適配器來使用它。

這將存儲我們的視圖,以便我們不必多次撥打row.findViewById。我們應該在哪裏設置它?當我們第一次誇大觀點時。我們在哪裏存儲它?視圖有一個自定義的「標籤」字段,可用於存儲關於視圖的元信息 - 正是我們想要的!然後,如果我們已經看到了這種觀點,我們只需要查找的標籤,而不是查找每個行內觀點..

所以getView()內部的if語句變成:

//If the row is null, it means that we aren't recycling anything - so we have 
//to inflate the layout ourselves. 
ViewHolder holder = null; 
if(row == null) { 
    LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
    row = inflater.inflate(R.layout.list_item, parent, false); 
    //Now create the ViewHolder 
    holder = new ViewHolder(); 
    //and set its textView field to the proper value 
    holder.textView = (TextView) row.findViewById(R.id.listItemTextView); 
    //and store it as the 'tag' of our view 
    row.setTag(holder); 
} else { 
    //We've already seen this one before! 
    holder = (ViewHolder) row.getTag(); 
} 

現在,我們只需更新holder.textView的文本值,因爲它已經是對回收視圖的引用了!所以我們的最終適配器代碼變爲:

public View getView(int position, View convertView, ViewGroup parent) { 
    //Here, position is the index in the list, the convertView is the view to be 
    //recycled (or created), and parent is the ListView itself. 

    //Grab the convertView as our row of the ListView 
    View row = convertView; 

    //If the row is null, it means that we aren't recycling anything - so we have 
    //to inflate the layout ourselves. 
    ViewHolder holder = null; 
    if(row == null) { 
     LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
     row = inflater.inflate(R.layout.list_item, parent, false); 
     //Now create the ViewHolder 
     holder = new ViewHolder(); 
     //and set its textView field to the proper value 
     holder.textView = (TextView) row.findViewById(R.id.listItemTextView); 
     //and store it as the 'tag' of our view 
     row.setTag(holder); 
    } else { 
     //We've already seen this one before! 
     holder = (ViewHolder) row.getTag(); 
    } 

    //Grab the item to be rendered. In this case, I'm just using a string, but 
    //you will use your underlying object type. 
    final String item = getItem(position); 

    //And update the ViewHolder for this View's text to the correct text. 
    holder.textView.setText(item); 

    //and return the row 
    return row; 
} 

我們完成了!

有些事情要考慮:

  1. 如何做到這一點的變化,如果你有要更改行多個視圖?作爲一個挑戰,讓一個ListView每行有兩個TextView對象和ImageView
  2. 當調試的ListView,查了幾件事情,讓你可以真正看到發生了什麼事情:
    1. 多少次ViewHolder的構造函數被調用。
    2. holder.textView.getText()價值是什麼,你在getView()
+0

thnx,真棒答案 – Gorets

+0

沒問題 - 讓我知道,如果一切都合理,ViewHolder的東西肯定讓我困惑了一陣子,當時我第一次瞭解它。 – mindvirus

+0

哇。優秀的答案。作爲你的挑戰;你能用一些參考來改進它嗎? =) – Qw4z1

2

正如我們每次滾動列表時填充行併爲每行創建新行視圖,我們都需要初始化視圖持有者。就像我有兩行TextView然後,

static class ViewHolder { 
     protected TextView title; 
     protected TextView type; 

    } 


    public View getView(int position, View convertView, ViewGroup parent) { 
      View view = null; 
      if (convertView == null) { 
       LayoutInflater inflator = context.getLayoutInflater(); 
       view = inflator.inflate(R.layout.feeds_rowview, null); 
       final ViewHolder viewHolder = new ViewHolder(); 
       view.setTag(viewHolder); 
       viewHolder.title = (TextView) view.findViewById(R.id.Title); 
       viewHolder.type = (TextView) view.findViewById(R.id.Type); 

      } else { 
       view = convertView; 
      } 

      ViewHolder holder = (ViewHolder) view.getTag(); 
      holder.title.setText(list.get(position).getTitle()); 
      holder.type.setText(list.get(position).getType()); 

      return view; 
    } 
+0

不,每個視圖都有不同的值。因爲我們在行中有不同的數據。 –