listView中多個listItem布局時,convertView緩存及使用
原創教程,轉載請保留出處:https://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;
}
public View getView(int position, View convertView, ViewGroup parent) {
View view = new Xxx(...);
... ...
return view;
}
描述:
以構造ListView的BaseAdapter為例,在BaseAdapter中提供了方法:
public View getView(int position, View convertView, ViewGroup parent){ }
來向ListView提供每一個item所需要的view對象。初始時ListView會從BaseAdapter中根據當前的屏幕布局實例化一定數量的view對象,同時ListView會將這些view對象緩存起來。當向上滾動ListView時,原先位於最上麵的list item的view對象會被回收,然後被用來構造新出現的最下麵的list item。這個構造過程就是由getView()方法完成的,getView()的第二個形參 View convertView就是被緩存起來的list item的view對象(初始化時緩存中沒有view對象則convertView是null)。
由此可以看出,如果我們不去使用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;
}
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就不太好用了,convertView在Item為單一的布局時,能夠回收並重用,但是多個Item布局時,convertView的回收和重用會出現問題。
Listview中有3種Item布局,即使convertView緩存了一些布局,但是在重構時,根本不知道怎麼樣去讓convertView返回你所需要的布局,這時你需要讓adapter知道我當前有哪些布局,我重構Item時的布局選取規則,好讓convertView能返回你需要的布局
需要重寫一下兩個函數
@Override
public int getItemViewType(int position) {}
@Override
public int getViewTypeCount() {}
上述兩個函數的作用這如它的名字,得到Item的樣式,得到所有的樣式數量
下麵直接上代碼,就是上圖的實現代碼:
package com.bestv.listViewTest; import java.util.ArrayList; import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.CheckBox; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.TextView; public class listViewTest extends Activity { /** Called when the activity is first created. */ ListView listView; MyAdapter listAdapter; ArrayList<String> listString; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); listView = (ListView)this.findViewById(R.id.listview); listString = new ArrayList<String>(); for(int i = 0 ; i < 100 ; i++) { listString.add(Integer.toString(i)); } listAdapter = new MyAdapter(this); listView.setAdapter(listAdapter); } class MyAdapter extends BaseAdapter{ Context mContext; LinearLayout linearLayout = null; LayoutInflater inflater; TextView tex; final int VIEW_TYPE = 3; final int TYPE_1 = 0; final int TYPE_2 = 1; final int TYPE_3 = 2; public MyAdapter(Context context) { // TODO Auto-generated constructor stub mContext = context; inflater = LayoutInflater.from(mContext); } @Override public int getCount() { // TODO Auto-generated method stub return listString.size(); } //每個convert view都會調用此方法,獲得當前所需要的view樣式 @Override public int getItemViewType(int position) { // TODO Auto-generated method stub int p = position%6; if(p == 0) return TYPE_1; else if(p < 3) return TYPE_2; else if(p < 6) return TYPE_3; else return TYPE_1; } @Override public int getViewTypeCount() { // TODO Auto-generated method stub return 3; } @Override public Object getItem(int arg0) { // TODO Auto-generated method stub return listString.get(arg0); } @Override public long getItemId(int position) { // TODO Auto-generated method stub return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { // TODO Auto-generated method stub viewHolder1 holder1 = null; viewHolder2 holder2 = null; viewHolder3 holder3 = null; int type = getItemViewType(position); //無convertView,需要new出各個控件 if(convertView == null) { Log.e("convertView = ", " NULL"); //按當前所需的樣式,確定new的布局 switch(type) { case TYPE_1: convertView = inflater.inflate(R.layout.listitem1, parent, false); holder1 = new viewHolder1(); holder1.textView = (TextView)convertView.findViewById(R.id.textview1); holder1.checkBox = (CheckBox)convertView.findViewById(R.id.checkbox); Log.e("convertView = ", "NULL TYPE_1"); convertView.setTag(holder1); break; case TYPE_2: convertView = inflater.inflate(R.layout.listitem2, parent, false); holder2 = new viewHolder2(); holder2.textView = (TextView)convertView.findViewById(R.id.textview2); Log.e("convertView = ", "NULL TYPE_2"); convertView.setTag(holder2); break; case TYPE_3: convertView = inflater.inflate(R.layout.listitem3, parent, false); holder3 = new viewHolder3(); holder3.textView = (TextView)convertView.findViewById(R.id.textview3); holder3.imageView = (ImageView)convertView.findViewById(R.id.imageview); Log.e("convertView = ", "NULL TYPE_3"); convertView.setTag(holder3); break; } } else { //有convertView,按樣式,取得不用的布局 switch(type) { case TYPE_1: holder1 = (viewHolder1) convertView.getTag(); Log.e("convertView !!!!!!= ", "NULL TYPE_1"); break; case TYPE_2: holder2 = (viewHolder2) convertView.getTag(); Log.e("convertView !!!!!!= ", "NULL TYPE_2"); break; case TYPE_3: holder3 = (viewHolder3) convertView.getTag(); Log.e("convertView !!!!!!= ", "NULL TYPE_3"); break; } } //設置資源 switch(type) { case TYPE_1: holder1.textView.setText(Integer.toString(position)); holder1.checkBox.setChecked(true); break; case TYPE_2: holder2.textView.setText(Integer.toString(position)); break; case TYPE_3: holder3.textView.setText(Integer.toString(position)); holder3.imageView.setBackgroundResource(R.drawable.icon); break; } return convertView; } } //各個布局的控件資源 class viewHolder1{ CheckBox checkBox; TextView textView; } class viewHolder2{ TextView textView; } class viewHolder3{ ImageView imageView; TextView textView; } }
在getView()中需要將不同布局進行緩存和適配,係統在判斷是否有convertView時,會自動去調用getItemViewType (int position) ,查看是否已經有緩存的該類型的布局,從而進入if(convertView == null)和else{}的判斷。期間需要做的是convertView.setTag(holder3),以便在convertView重用時可以直接拿到該布局的控件,holder3 = (viewHolder3) convertView.getTag()。到這一步,convertView的回收和重用就已經寫好了,接下來隻需要對你的不同的控件進行設置就行了。
最後更新:2017-04-02 15:28:26