閱讀462 返回首頁    go 阿裏雲 go 技術社區[雲棲]


在listView中多個listItem布局時,convertView緩存及使用

今天我在寫項目的時候遇到了一個問題,就是我需要在ListView中使用多種布局。但是如果用之前我在教程中提到的保證效率的View重用,結果每次在convertView不為空需要重新加載之後,所有的item在找尋不同布局的時候就會亂七八糟,如果犧牲效率的話,明顯感覺項目在我配置不低的手機都出現了卡頓,正苦思冥想的時候,在網上發現了這樣一種解決方法,真乃神來之筆,所以轉載過來分享給大家。

轉載地址: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;
}

描述:
  
以構造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的樣式,得到所有的樣式數量
下麵直接上代碼,就是上圖的實現代碼:

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的回收和重用就已經寫好了,接下來隻需要對你的不同的控件進行設置就行了。

轉載地址:https://www.eoeandroid.com/thread-72369-1-1.html

最後更新:2017-04-03 05:39:37

  上一篇:go 存儲過程--機房收費係統
  下一篇:go Linux內核剖析 之 進程地址空間(一)