listView中多個listItem佈局時,convertView快取及使用

鴨脖發表於2012-08-09
原創教程,轉載請保留出處:http://www.eoeandroid.com/thread-72369-1-1.html
最近有需求需要在listView中載入不同的listItem佈局,開始沒有使用convertView,載入了多個item後導致了記憶體洩露,所以回來研究convertView在多個listItem佈局時的快取及應用,並且和大家分享
構造Adapter時,沒有使用快取的 convertView,導致記憶體洩露
示例程式碼:
public View getView(int position, View convertView, ViewGroup parent) {
  View view = new Xxx(...);
  ... ...
  return view;
}

描述:
  
以構造ListViewBaseAdapter為例,在BaseAdapter中提供了方法:


public View getView(int position, View convertView, ViewGroup parent){ }

來向ListView提供每一個item所需要的view物件。初始時ListView會從BaseAdapter中根據當前的螢幕佈局例項化一定數量的view物件,同時ListView會將這些view物件快取起來。當向上滾動ListView時,原先位於最上面的list itemview物件會被回收,然後被用來構造新出現的最下面的list item。這個構造過程就是由getView()方法完成的,getView()的第二個形參 View convertView就是被快取起來的list itemview物件(初始化時快取中沒有view物件則convertViewnull)
  
由此可以看出,如果我們不去使用convertView,而是每次都在getView()中重新例項化一個View物件的話,即浪費資源也浪費時間,也會使得記憶體佔用越來越大。
修正示例程式碼:
public View getView(int position, View convertView, ViewGroup parent) {
  View view = null;
  if (convertView != null) {
  view = convertView;
  ...
  } else {
  view = new Xxx(...);
  ...
  }
  return view;
}
上述程式碼很好的解決了記憶體洩露的問題,使用convertView回收一些佈局供下面重構是使用。
但是如果出現如下圖的需求,convertView就不太好用了,convertViewItem為單一的佈局時,能夠回收並重用,但是多個Item佈局時,convertView的回收和重用會出現問題。





Listview中有3Item佈局,即使convertView快取了一些佈局,但是在重構時,根本不知道怎麼樣去讓convertView返回你所需要的佈局,這時你需要讓adapter知道我當前有哪些佈局,我重構Item時的佈局選取規則,好讓convertView能返回你需要的佈局
需要重寫一下兩個函式
@Override
public int getItemViewType(int position) {}
官網解釋如下,不解釋了
Get the type of View that will be created by getView(int, android.view.View, android.view.ViewGroup)]getView(int, View, ViewGroup) for the specified item.

Parameters
position The position of the item within the adapter's data set whose view type we want.

Returns


  • An integer representing the type of View. Two views should share the same type if one can be converted to the other in getView(int, android.view.View, android.view.ViewGroup)getView(int, View, ViewGroup). Note: Integers must be in the range 0 to getViewTypeCount() - 1. IGNORE_ITEM_VIEW_TYPE can also be returned.



@Override
public int getViewTypeCount() {}

Get the type of View that will be created by getView(int, android.view.View, android.view.ViewGroup)getView(int, View, ViewGroup) for the specified item.

Parameters
position The position of the item within the adapter's data set whose view type we want.

Returns


  • An integer representing the type of View. Two views should share the same type if one can be converted to the other in getView(int, android.view.View, android.view.ViewGroup)getView(int, View, ViewGroup). Note: Integers must be in the range 0 to getViewTypeCount() - 1. IGNORE_ITEM_VIEW_TYPE can also be returned.




上述兩個函式的作用這如它的名字,得到Item的樣式,得到所有的樣式數量
下面直接上程式碼,就是上圖的實現程式碼:
  1. package com.bestv.listViewTest;

  2. import java.util.ArrayList;

  3. import android.app.Activity;
  4. import android.content.Context;
  5. import android.os.Bundle;
  6. import android.util.Log;
  7. import android.view.LayoutInflater;
  8. import android.view.View;
  9. import android.view.ViewGroup;
  10. import android.widget.BaseAdapter;
  11. import android.widget.CheckBox;
  12. import android.widget.ImageView;
  13. import android.widget.LinearLayout;
  14. import android.widget.ListView;
  15. import android.widget.TextView;

  16. public class listViewTest extends Activity {
  17. /** Called when the activity is first created. */
  18. ListView listView;
  19. MyAdapter listAdapter;
  20. ArrayList<String> listString;

  21. @Override
  22. public void onCreate(Bundle savedInstanceState) {
  23. super.onCreate(savedInstanceState);
  24. setContentView(R.layout.main);
  25. listView = (ListView)this.findViewById(R.id.listview);
  26. listString = new ArrayList<String>();
  27. for(int i = 0 ; i < 100 ; i++)
  28. {
  29. listString.add(Integer.toString(i));
  30. }
  31. listAdapter = new MyAdapter(this);
  32. listView.setAdapter(listAdapter);
  33. }

  34. class MyAdapter extends BaseAdapter{

  35. Context mContext;
  36. LinearLayout linearLayout = null;
  37. LayoutInflater inflater;
  38. TextView tex;
  39. final int VIEW_TYPE = 3;
  40. final int TYPE_1 = 0;
  41. final int TYPE_2 = 1;
  42. final int TYPE_3 = 2;

  43. public MyAdapter(Context context) {
  44. // TODO Auto-generated constructor stub
  45. mContext = context;
  46. inflater = LayoutInflater.from(mContext);
  47. }

  48. @Override
  49. public int getCount() {
  50. // TODO Auto-generated method stub
  51. return listString.size();
  52. }

  53. //每個convert view都會呼叫此方法,獲得當前所需要的view樣式
  54. @Override
  55. public int getItemViewType(int position) {
  56. // TODO Auto-generated method stub
  57. int p = position%6;
  58. if(p == 0)
  59. return TYPE_1;
  60. else if(p < 3)
  61. return TYPE_2;
  62. else if(p < 6)
  63. return TYPE_3;
  64. else
  65. return TYPE_1;

  66. }

  67. @Override
  68. public int getViewTypeCount() {
  69. // TODO Auto-generated method stub
  70. return 3;
  71. }

  72. @Override
  73. public Object getItem(int arg0) {
  74. // TODO Auto-generated method stub
  75. return listString.get(arg0);
  76. }

  77. @Override
  78. public long getItemId(int position) {
  79. // TODO Auto-generated method stub
  80. return position;
  81. }

  82. @Override
  83. public View getView(int position, View convertView, ViewGroup parent) {
  84. // TODO Auto-generated method stub
  85. viewHolder1 holder1 = null;
  86. viewHolder2 holder2 = null;
  87. viewHolder3 holder3 = null;
  88. int type = getItemViewType(position);


  89. //無convertView,需要new出各個控制元件
  90. if(convertView == null)

  91. Log.e("convertView = ", " NULL");

  92. //按當前所需的樣式,確定new的佈局
  93. switch(type)
  94. {
  95. case TYPE_1:
  96. convertView = inflater.inflate(R.layout.listitem1, parent, false);
  97. holder1 = new viewHolder1();
  98. holder1.textView = (TextView)convertView.findViewById(R.id.textview1);
  99. holder1.checkBox = (CheckBox)convertView.findViewById(R.id.checkbox);
  100. Log.e("convertView = ", "NULL TYPE_1");
  101. convertView.setTag(holder1);
  102. break;
  103. case TYPE_2:
  104. convertView = inflater.inflate(R.layout.listitem2, parent, false);
  105. holder2 = new viewHolder2();
  106. holder2.textView = (TextView)convertView.findViewById(R.id.textview2);
  107. Log.e("convertView = ", "NULL TYPE_2");
  108. convertView.setTag(holder2);
  109. break;
  110. case TYPE_3:
  111. convertView = inflater.inflate(R.layout.listitem3, parent, false);
  112. holder3 = new viewHolder3();
  113. holder3.textView = (TextView)convertView.findViewById(R.id.textview3);
  114. holder3.imageView = (ImageView)convertView.findViewById(R.id.imageview);
  115. Log.e("convertView = ", "NULL TYPE_3");
  116. convertView.setTag(holder3);
  117. break;
  118. }
  119. }
  120. else
  121. {
  122. //有convertView,按樣式,取得不用的佈局
  123. switch(type)
  124. {
  125. case TYPE_1:
  126. holder1 = (viewHolder1) convertView.getTag();
  127. Log.e("convertView !!!!!!= ", "NULL TYPE_1");
  128. break;
  129. case TYPE_2:
  130. holder2 = (viewHolder2) convertView.getTag();
  131. Log.e("convertView !!!!!!= ", "NULL TYPE_2");
  132. break;
  133. case TYPE_3:
  134. holder3 = (viewHolder3) convertView.getTag();
  135. Log.e("convertView !!!!!!= ", "NULL TYPE_3");
  136. break;
  137. }
  138. }

  139. //設定資源
  140. switch(type)
  141. {
  142. case TYPE_1:
  143. holder1.textView.setText(Integer.toString(position));
  144. holder1.checkBox.setChecked(true);
  145. break;
  146. case TYPE_2:
  147. holder2.textView.setText(Integer.toString(position));
  148. break;
  149. case TYPE_3:
  150. holder3.textView.setText(Integer.toString(position));
  151. holder3.imageView.setBackgroundResource(R.drawable.icon);
  152. break;
  153. }


  154. return convertView;
  155. }

  156. }


  157. //各個佈局的控制元件資源

  158. class viewHolder1{
  159. CheckBox checkBox;
  160. TextView textView;
  161. }
  162. class viewHolder2{
  163. TextView textView;
  164. }
  165. class viewHolder3{
  166. ImageView imageView;
  167. TextView textView;
  168. }
  169. }
複製程式碼

     在getView()中需要將不同佈局進行快取和適配,系統在判斷是否有convertView時,會自動去呼叫getItemViewType (int position) ,檢視是否已經有快取的該型別的佈局,從而進入if(convertView == null)和else{}的判斷。期間需要做的是convertView.setTag(holder3),以便在convertView重用時可以直接拿到該佈局的控制元件,holder3 = (viewHolder3) convertView.getTag()。到這一步,convertView的回收和重用就已經寫好了,接下來只需要對你的不同的控制元件進行設定就行了。





如有不正確或者我理解錯誤的地方,大家輕拍!

相關文章