Android ListView – Holder模式和getItemViewType
在Mobile開發:Android-ListView中介紹了在Android中如何使用ListView,以及為數據模型自定義Item的UI. 這一篇要將兩個方麵的內容:Holder模式和getItemViewType的應用。
Holder模式
模式這個詞語是通過GOF的《設計模式》這本書而引入軟件設計領域的。在軟件設計領域裏麵,模式是指為解決特定問題而存在的通用解決方案。這個特定的問題如果在軟件設計領域廣泛存在,那麼該解決方案就有記錄和傳播的價值。
Holder模式要解決的是性能問題:
場景:在我們的Adapter中每次都要從row中通過findViewById來找到子控件,然後設置值。如果row的布局比較複雜,或者row的數目特別多。這個查找就要不斷發生。從而導致性能問題。
方案:在row第一次被構建出來的時候,調用findViewById, 通過Holder對象存儲起來,然後把Holder對象通過row.setTag方法,直接緩存在row上。這樣下次就不用在查找了。
看看下麵的代碼:
@Override public View getView(int position, View convertView, ViewGroup parent) { try { Restaurant item = this.getItem(position); View row = convertView; RestaurantListViewItemHolder holder=null; if (row == null) { LayoutInflater inflater = LayoutInflater .from(this.getContext()); if(!item.isGoldenBrand()) row = inflater .inflate(R.layout.restaurant_list_view_item, null); else row=inflater.inflate(R.layout.restaurant_list_view_item_golden, null); holder = createRestaurantListViewItemHolder(row,item.isGoldenBrand()); row.setTag(holder); }else{ holder=(RestaurantListViewItemHolder)row.getTag(); } holder.UpdateUI(item); return row; } catch (Exception e) { Log.e(LogTags.RestaurantListAdapter, e.getLocalizedMessage(), e); } return super.getView(position, convertView, parent); } /** * Create a holder for a row in list,when the row is first loaded. * @param row * @return */ private RestaurantListViewItemHolder createRestaurantListViewItemHolder( View row,boolean isGoldenBrand) { TextView txtName = (TextView) row .findViewById(R.id.restaurant_name); TextView txtPhoneNR = (TextView) row .findViewById(R.id.restaurant_phone_nr); TextView txtLocation = (TextView) row .findViewById(R.id.restaurant_address); ImageView imgIcon=(ImageView) row.findViewById(R.id.restaurent_icon); TextView txtDescription=null; if(isGoldenBrand) txtDescription=(TextView)row.findViewById(R.id.restaurant_description_direct); return new RestaurantListViewItemHolder(txtPhoneNR,txtName,txtLocation, imgIcon,txtDescription); }
上麵的代碼由於混合了和getItemViewType有關的內容。所以看起來有點和上篇中不同。其主要代碼在第15,16,18,21行。
- 第15行:通過調用createRestaurantListViewItemHolder來構造一個Holder對象。
- 第16行:調用row.setTag將Holder關聯到row對象上。
- 第18行:如果row對象不是新構建的,直接獲取row上的Tag,這個Tag對象就是我們在第16行存儲的RestaurantListViewItemHolder對象。
- 第21行:調用holder.UpdateUI來更新UI。
上麵的代碼中已經用到了RestaurantListViewItemHolder,下麵是代碼文件。邏輯非常的簡單。
package net.lxzhu.mobile; import android.widget.ImageView; import android.widget.TextView; public class RestaurantListViewItemHolder { private TextView phoneNumberUI; private TextView nameUI; private TextView addressUI; private ImageView iconUI; private TextView descriptionUI; public RestaurantListViewItemHolder(TextView phoneNumberUI, TextView nameUI, TextView addressUI, ImageView iconUI,TextView descriptionUI) { this.phoneNumberUI=phoneNumberUI; this.nameUI=nameUI; this.addressUI=addressUI; this.iconUI=iconUI; this.descriptionUI=descriptionUI; } public void UpdateUI(Restaurant data){ this.phoneNumberUI.setText(data.getPhoneNR()); this.nameUI.setText(data.getName()); this.addressUI.setText(data.getAddress()); this.iconUI.setImageResource(R.drawable.ic_launcher); if(this.descriptionUI!=null) this.descriptionUI.setText(data.getDescription()); } }
getItemViewType
假設我們的UI設計有這樣一個需求。如果一家餐館被評為金牌場館,那麼它可以直接將最近的一條點評顯示在列表中,這樣用戶不用選中或者點擊就可以看到評價了。
這就是為什麼我們上麵的代碼中出現了isGoldenBrand這樣的代碼。到目前為止,我們的ListView中的每個Item都是一樣的。而這個需求卻讓我們根據數據的不同出現了不同的布局。AdapterView已經提供了支持,我們隻需要:
- 重寫getViewTypeCount方法,返回可能出現的布局的數目。
- 重寫getItemViewType方法,這個方法返回0到getViewTypeCount()-1之間的數字或者IGNORE_ITEM_VIEW_TYPE. 這個方法返回的id必須要和getView方法中返回的View匹配:在getView中返回相同類型View的position, getItemViewType應該返回同一個id.
- 在getView中根據數據不同,返回不同的View。
下麵是getViewTypeCount和getItemViewType的代碼,getView的代碼前麵已經給出,這裏就不在重複了。
@Override public int getViewTypeCount() { return 2; } @Override public int getItemViewType(int position) { Restaurant data=this.getItem(position); if(!data.isGoldenBrand()) return 0; else return 1; }
其實我們這裏的需求不使用這種模式也可以開發出來。但是如果需求中對金牌餐館的布局和普通餐館布局有很大的不同的話。這個功能就非常實用了。下麵是修改後的代碼的執行結果。
最後更新:2017-04-02 15:28:26