原创教程,转载请保留出处: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;
}
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) {}
官网解释如下,不解释了
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的回收和重用就已经写好了,接下来只需要对你的不同的控件进行设置就行了。
还有一种方法:
在主布局文件中包含一个LinearLayout. 并且设置android:orientation="vertical"
定义两种不同的布局文件,然后代码中,根据不同条件调用.addview向LinearLayout加载就可以了
在实践中遇到的问题:
发现在加载的时候,会出现classcastexception,不同的viewholder在转换的时候会出现这个问题,后来找了原因,有这样解释的:http://www.eoeandroid.com/thread-263957-1-1.html
所以就捕捉这个异常,
if(convertView == null){//无convertView,需要new出各个控件 //按当前所需的样式,确定new的布局 switch (type) { case ARTICLE : discussesInflater = LayoutInflater.from(discussescontext); convertView = discussesInflater.inflate(R.layout.my_favourate_detail_item, null); holder1.wvFavouriteDetails = (WebView)convertView.findViewById(R.id.wv_my_favourite_details); holder1.tv_subject_my_favourite_details = (TextView)convertView.findViewById(R.id.tv_subject_my_favourite_details); holder1.tv_more_my_favourite_details1= (TextView)convertView.findViewById(R.id.tv_more_my_favourite_details1); holder1.tv_more_my_favourite_details2= (TextView)convertView.findViewById(R.id.tv_more_my_favourite_details2); convertView.setTag(holder1); break; case COMMENT: discussesInflater = LayoutInflater.from(discussescontext); convertView = discussesInflater.inflate(R.layout.discussespic_item, null); holder2.discussespic = (ImageView) convertView.findViewById(R.id.iv_discussespic); holder2.discussestext = (TextView) convertView.findViewById(R.id.tv_discussestext); holder2.nametext = (TextView) convertView.findViewById(R.id.tv_nametext); holder2.discussespic = (com.haojiazhang.widget.CircularImage)convertView.findViewById(R.id.iv_discussespic); convertView.setTag(holder2); break; default: break; } }else {//有convertView,按样式,取得不用的布局 switch (type) { case ARTICLE : try { holder1 = (ViewHolder1) convertView.getTag(); if (holder1 == null) { holder1 = new ViewHolder1(); convertView = LayoutInflater.from(discussescontext).inflate(R.layout.my_favourate_detail_item, null); holder1.wvFavouriteDetails = (WebView)convertView.findViewById(R.id.wv_my_favourite_details); holder1.tv_subject_my_favourite_details = (TextView)convertView.findViewById(R.id.tv_subject_my_favourite_details); holder1.tv_more_my_favourite_details1= (TextView)convertView.findViewById(R.id.tv_more_my_favourite_details1); holder1.tv_more_my_favourite_details2= (TextView)convertView.findViewById(R.id.tv_more_my_favourite_details2); convertView.setTag(holder1); } } catch (Exception e) { holder1 = new ViewHolder1(); convertView = LayoutInflater.from(discussescontext).inflate(R.layout.my_favourate_detail_item, null); holder1.wvFavouriteDetails = (WebView)convertView.findViewById(R.id.wv_my_favourite_details); holder1.tv_subject_my_favourite_details = (TextView)convertView.findViewById(R.id.tv_subject_my_favourite_details); holder1.tv_more_my_favourite_details1= (TextView)convertView.findViewById(R.id.tv_more_my_favourite_details1); holder1.tv_more_my_favourite_details2= (TextView)convertView.findViewById(R.id.tv_more_my_favourite_details2); convertView.setTag(holder1); } break; case COMMENT: try { holder2 = (ViewHolder2) convertView.getTag(); if (holder2 == null) { holder2 = new ViewHolder2(); convertView = LayoutInflater.from(discussescontext).inflate(R.layout.discussespic_item, null); holder2.discussespic = (ImageView) convertView.findViewById(R.id.iv_discussespic); holder2.discussestext = (TextView) convertView.findViewById(R.id.tv_discussestext); holder2.nametext = (TextView) convertView.findViewById(R.id.tv_nametext); holder2.discussespic = (com.haojiazhang.widget.CircularImage)convertView.findViewById(R.id.iv_discussespic); convertView.setTag(holder2); } } catch (Exception e) { holder2 = new ViewHolder2(); convertView = LayoutInflater.from(discussescontext).inflate(R.layout.discussespic_item, null); holder2.discussespic = (ImageView) convertView.findViewById(R.id.iv_discussespic); holder2.discussestext = (TextView) convertView.findViewById(R.id.tv_discussestext); holder2.nametext = (TextView) convertView.findViewById(R.id.tv_nametext); holder2.discussespic = (com.haojiazhang.widget.CircularImage)convertView.findViewById(R.id.iv_discussespic); convertView.setTag(holder2); } break; default: break; } }