最近接触到了listView中多个Item复用的问题。listView使我们Android开发中非常常见的一个控件,可以说任何一款应用都会使用到他,所有呢,这个控件也被别人研究的比较多。常用的一般复用方法,大家应该都清楚。convertView的复用,使用ViewHolder,使用LruCache,异步加载等等。这里就是记录我开发当中遇到的listView的多个item复用的方法和一些处理的坑。。
首先呢,假定我们的listView中总共有2种类型的Item,这2中类型完全不同,我们设定View1,View2。假定我们定义2个ViewHolder。一个TextHolder,一个AudioHolder。
class TextViewHolder {
public CircleImageView circle_img;
public TextView name;
public TextView title;
public TextView praiseCount;
public TextView praiseCount_praised;
public TextView tv_time;
public TextView tv_play;
public CircleImageView text_hidden_avatar;
public TextView answer_detail_text;
public LinearLayout text_layout;
public View zd_audio_layout;
public View v_miao;
public ProgressBar pb;
}
class AudioViewHolder {
public CircleImageView circle_img;
public TextView tv_time;
public TextView name;
public TextView title;
public TextView audio_prise_num_text;
public TextView audio_prise_num_text_prised;
public TextView tv_play;
public RelativeLayout audio_layout;
public View zd_audio_layout;
public View v_miao;
public ProgressBar pb;
}
我们在getView的方法中复用convertView的代码
if (convertView == null) {
switch (type) {
case AUDIO_TYPE:
audioHolder = new AudioViewHolder();
convertView = inflater.inflate(
R.layout.audio_answer_detail_listview_item, null);
audioHolder.circle_img = (CircleImageView) convertView.findViewById(R.id.iv_avatar);
audioHolder.name = (TextView) convertView.findViewById(R.id.audio_name);
audioHolder.tv_time = (TextView) convertView.findViewById(R.id.tv_time);
audioHolder.audio_prise_num_text_prised = (TextView) convertView.findViewById(R.id.audio_prise_num_text_prised);
audioHolder.audio_prise_num_text = (TextView) convertView.findViewById(R.id.audio_prise_num_text);
audioHolder.title = (TextView) convertView.findViewById(R.id.audio_title);
audioHolder.tv_play = (TextView) convertView.findViewById(R.id.tv_play);
audioHolder.audio_layout = (RelativeLayout) convertView.findViewById(R.id.audio_layout);
audioHolder.zd_audio_layout = convertView.findViewById(R.id.zd_audio_layout);
audioHolder.v_miao = convertView.findViewById(R.id.v_miao);
audioHolder.pb = (ProgressBar) convertView.findViewById(R.id.pb);
convertView.setTag(audioHolder);
break;
case TEXT_TYPE:
textHolder = new TextViewHolder();
convertView = inflater.inflate(
R.layout.text_answer_detail_listview_item, null);
textHolder.circle_img = (CircleImageView) convertView.findViewById(R.id.iv_avatar);
textHolder.name = (TextView) convertView.findViewById(R.id.text_answer_name);
textHolder.tv_time = (TextView) convertView.findViewById(R.id.tv_time);
textHolder.praiseCount = (TextView) convertView.findViewById(R.id.praiseCount);
textHolder.praiseCount_praised = (TextView) convertView.findViewById(R.id.praiseCount_praised);
textHolder.title = (TextView) convertView.findViewById(R.id.text_answer_title);
textHolder.tv_play = (TextView) convertView.findViewById(R.id.tv_play);
textHolder.text_hidden_avatar = (CircleImageView) convertView.findViewById(R.id.text_hidden_avatar);
textHolder.answer_detail_text = (TextView) convertView.findViewById(R.id.answer_detail_text);
textHolder.text_layout = (LinearLayout)convertView.findViewById(R.id.text_layout);
textHolder.zd_audio_layout = convertView.findViewById(R.id.zd_audio_layout);
textHolder.v_miao = convertView.findViewById(R.id.v_miao);
textHolder.pb = (ProgressBar) convertView.findViewById(R.id.pb);
convertView.setTag(textHolder);
break;
}
} else {
switch (type) {
case AUDIO_TYPE:
audioHolder = (AudioViewHolder) convertView.getTag();
break;
case TEXT_TYPE:
textHolder = (TextViewHolder) convertView.getTag();
break;
}
}
这是否很符合逻辑,也很简单是吧,但是运行的时候当你listView中2种View都存在的时候,滑动listview程序就会崩溃,提示啥AudioHolder不能强转为TextHolder,或者TextHolder不能强转为AudioHolder。这明显是复用的问题了。当listview中只有TextHolder或者只有AudioHolder的时候程序当然不会崩溃的。要解决这样的问题有2种方法。但是还是推崇第一种方法,因为这种方法比较通用,简单。
方法一:复写Adapter中的getItemViewType(int position) 和getViewTypeCount()方法
@Override
public int getItemViewType(int position) {
AnswerDetailResponseBean.Answer answer = mData.get(position).answer;
if (answer.answerType == 0) { // 语音
return AUDIO_TYPE;
} else if (answer.answerType == 1) { // 文本
return TEXT_TYPE;
}
return -1;
}
@Override
public int getViewTypeCount() {
return 2; //有几种类型的type就返回几种类型 同时上面的getItemViewType也相应的改变
}
只需要复写这两个方法就ok了。而且是google通用的。我目前还没有弄清楚为什么复写getViewTypeCount方法就能让系统找到我们需要的ViewHolder类型,目前还在研究源码当中,弄清楚再来补充。
方法二:先看代码 后面再解释。
if (convertView == null) {
switch (type) {
case AUDIO_TYPE:
audioHolder = new AudioViewHolder();
convertView = inflater.inflate(
R.layout.audio_answer_detail_listview_item, null);
audioHolder.circle_img = (CircleImageView) convertView.findViewById(R.id.iv_avatar);
audioHolder.name = (TextView) convertView.findViewById(R.id.audio_name);
audioHolder.tv_time = (TextView) convertView.findViewById(R.id.tv_time);
audioHolder.audio_prise_num_text_prised = (TextView) convertView.findViewById(R.id.audio_prise_num_text_prised);
audioHolder.audio_prise_num_text = (TextView) convertView.findViewById(R.id.audio_prise_num_text);
audioHolder.title = (TextView) convertView.findViewById(R.id.audio_title);
audioHolder.tv_play = (TextView) convertView.findViewById(R.id.tv_play);
audioHolder.audio_layout = (RelativeLayout) convertView.findViewById(R.id.audio_layout);
audioHolder.zd_audio_layout = convertView.findViewById(R.id.zd_audio_layout);
audioHolder.v_miao = convertView.findViewById(R.id.v_miao);
audioHolder.pb = (ProgressBar) convertView.findViewById(R.id.pb);
convertView.setTag(audioHolder);
break;
case TEXT_TYPE:
textHolder = new TextViewHolder();
convertView = inflater.inflate(
R.layout.text_answer_detail_listview_item, null);
textHolder.circle_img = (CircleImageView) convertView.findViewById(R.id.iv_avatar);
textHolder.name = (TextView) convertView.findViewById(R.id.text_answer_name);
textHolder.tv_time = (TextView) convertView.findViewById(R.id.tv_time);
textHolder.praiseCount = (TextView) convertView.findViewById(R.id.praiseCount);
textHolder.praiseCount_praised = (TextView) convertView.findViewById(R.id.praiseCount_praised);
textHolder.title = (TextView) convertView.findViewById(R.id.text_answer_title);
textHolder.tv_play = (TextView) convertView.findViewById(R.id.tv_play);
textHolder.text_hidden_avatar = (CircleImageView) convertView.findViewById(R.id.text_hidden_avatar);
textHolder.answer_detail_text = (TextView) convertView.findViewById(R.id.answer_detail_text);
textHolder.text_layout = (LinearLayout)convertView.findViewById(R.id.text_layout);
textHolder.zd_audio_layout = convertView.findViewById(R.id.zd_audio_layout);
textHolder.v_miao = convertView.findViewById(R.id.v_miao);
textHolder.pb = (ProgressBar) convertView.findViewById(R.id.pb);
convertView.setTag(textHolder);
break;
}
} else {
switch (type) {
case AUDIO_TYPE:
if(convertView != null && convertView instanceof LinearLayout){
audioHolder = (AudioViewHolder) convertView.getTag();
}else{
if(audioHolder == null)
audioHolder = new AudioViewHolder();
convertView = inflater.inflate(
R.layout.audio_answer_detail_listview_item, null);
audioHolder.circle_img = (CircleImageView) convertView.findViewById(R.id.iv_avatar);
audioHolder.name = (TextView) convertView.findViewById(R.id.audio_name);
audioHolder.tv_time = (TextView) convertView.findViewById(R.id.tv_time);
audioHolder.audio_prise_num_text_prised = (TextView) convertView.findViewById(R.id.audio_prise_num_text_prised);
audioHolder.audio_prise_num_text = (TextView) convertView.findViewById(R.id.audio_prise_num_text);
audioHolder.title = (TextView) convertView.findViewById(R.id.audio_title);
audioHolder.tv_play = (TextView) convertView.findViewById(R.id.tv_play);
audioHolder.audio_layout = (RelativeLayout) convertView.findViewById(R.id.audio_layout);
audioHolder.zd_audio_layout = convertView.findViewById(R.id.zd_audio_layout);
audioHolder.v_miao = convertView.findViewById(R.id.v_miao);
audioHolder.pb = (ProgressBar) convertView.findViewById(R.id.pb);
convertView.setTag(audioHolder);
}
break;
case TEXT_TYPE:
if(convertView != null && convertView instanceof FrameLayout){
textHolder = (TextViewHolder) convertView.getTag();
}else{
if(textHolder == null)
textHolder = new TextViewHolder();
convertView = inflater.inflate(
R.layout.text_answer_detail_listview_item, null);
textHolder.circle_img = (CircleImageView) convertView.findViewById(R.id.iv_avatar);
textHolder.name = (TextView) convertView.findViewById(R.id.text_answer_name);
textHolder.tv_time = (TextView) convertView.findViewById(R.id.tv_time);
textHolder.praiseCount = (TextView) convertView.findViewById(R.id.praiseCount);
textHolder.praiseCount_praised = (TextView) convertView.findViewById(R.id.praiseCount_praised);
textHolder.title = (TextView) convertView.findViewById(R.id.text_answer_title);
textHolder.tv_play = (TextView) convertView.findViewById(R.id.tv_play);
textHolder.text_hidden_avatar = (CircleImageView) convertView.findViewById(R.id.text_hidden_avatar);
textHolder.answer_detail_text = (TextView) convertView.findViewById(R.id.answer_detail_text);
textHolder.text_layout = (LinearLayout)convertView.findViewById(R.id.text_layout);
textHolder.zd_audio_layout = convertView.findViewById(R.id.zd_audio_layout);
textHolder.v_miao = convertView.findViewById(R.id.v_miao);
textHolder.pb = (ProgressBar) convertView.findViewById(R.id.pb);
convertView.setTag(textHolder);
}
break;
}
}
就是在else的逻辑里面加了一层判断。代码的46 至48行首先判断了convertView不为null 并且convertView instanceof LinearLayout ,下面是convertView instanceof FrameLayout ,为什么这样写呢?假设我们AudioHolder的跟布局是LinearLayout,TextHolder的跟布局是FrameLayout,当我们的listVIew中只有TextHolder或者AudioHolder的时候就会走我们if后面的getTag()的逻辑。但是假设我们一个手机屏幕智能容纳4条Item的数据,而这4条全部是TextHolder的时候,当我们向上滑动的时候,第5条,第6条数据,全部是AudioHolder的数据,这时候想复用,convertView不为null,如果我们不加instanceof的逻辑判断,就会将我们的TextHolder转化为AudioHolder,而这样的强制转换时不允许的,也可以说是将LinearLayout强制转换成FrameLayout这样肯定会出错的,所有加了一层instanceof的判断。这样也可以作为一种方法。
但是方法二,可以在2种,3中item的时候使用,但是当Item种类很多的时候,这种方法也很难继续使用了,因为我们的布局也就那几种(我们可以自定义布局,但是我没有尝试过,你可以去试试),这种方法的适用性不好。所以还是推荐使用第一种方法。