在Android开发过程中,ListView是比较常用的控件,也是遇到问题比较多的控件。这2天开发时遇到些问题,在此做个记录,以作备考,同时也是抛砖引玉,和大家交流一下。
具体的项目中的内容不好直接说,这里简化一下需求:ListView中显示2中类型,一种个ImageView;一种是CheckBox + TextView,点击CheckBox时,TextView的文言会发生变化。具体请见下图:
图比较简陋,就是表明个意思,主要用来说明问题。
ListView里的item类定义如下:
package com.example.listviwtest;
public class ListItem {
public static final int TYPE_CHECKBOX = 0;
public static final int TYPE_IMAGE = 1;
public static final int TYPE_CNT = 2;
private int type = 0; <span style="white-space:pre"> </span>// item类型
private String title = null; <span style="white-space:pre"> </span>// TextView里需要显示的文言
private int imageId = R.drawable.ic_launcher; <span style="white-space:pre"> </span>// ImageViw里需要显示的图片
private boolean isChecked = false;<span style="white-space:pre"> </span> <span style="white-space:pre"> </span>// CheckBox是否选中
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getImageId() {
return imageId;
}
public void setImageId(int imageId) {
this.imageId = imageId;
}
public boolean isChecked() {
return isChecked;
}
public void setChecked(boolean isChecked) {
this.isChecked = isChecked;
}
}
ListView的Adapter定义如下:
package com.example.listviwtest;
import java.util.List;
import android.content.Context;
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.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
public class ListAdapter extends BaseAdapter {
private static final String TAG = "ListAdapter";
private List<ListItem> list = null;
private Context context = null;
private LayoutInflater inflater = null;
private ListView listView = null;
public ListAdapter(Context cxt, List<ListItem> list, ListView listView) {
this.context = cxt;
this.list = list;
this.listView = listView;
inflater = (LayoutInflater) cxt.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@Override
public int getCount() {
return list.size();
}
@Override
public ListItem getItem(int position) {
return list.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
ViewHolder holder = null;
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
final ListItem item = getItem(position);
if (null == convertView) {
if (ListItem.TYPE_CHECKBOX == item.getType()) {
convertView = inflater.inflate(R.layout.item_checkbox, null);
holder = new ViewHolder();
// !!!这里很关键,使用final不仅仅是语法需要,final后,会生成一个新的holder(fHolder),和原来的holder不一样,通过Log,大家可以清楚的看出来。
// 这样以后,fHolder就和此时生成的convertView建立了绑定关系。
final ViewHolder fHolder = holder;
holder.itemTitle = (TextView)convertView.findViewById(R.id.item_title);
holder.itemCheckBox = (CheckBox)convertView.findViewById(R.id.item_checkbox);
holder.itemCheckBox.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
String text = null;
if (isChecked) {
text = "Checked " + fHolder.position;
} else {
text = "NotChecked " + fHolder.position;
}
// 通过这里的Log可以清楚地看到position和holder.position,holder和fHolder的不同,
Log.d(TAG, "in onCheckedChanged, postition=" + position + ", holder.position=" + fHolder.position
+ ", holder=" + holder + ", fHolder=" + fHolder);
updateView(fHolder.position, text, isChecked);
}
});
convertView.setTag(holder);
} else if (ListItem.TYPE_IMAGE == item.getType()) {
convertView = inflater.inflate(R.layout.item_image, null, false);
holder = new ViewHolder();
holder.itemImage = (ImageView)convertView.findViewById(R.id.item_image);
convertView.setTag(holder);
}
} else {
holder = (ViewHolder)convertView.getTag();
}
holder.position = position; // 记录holder引用的list item在List<ListItem>中的位置,以便于点击CheckBox时,更新list item的内容。
if (ListItem.TYPE_CHECKBOX == getItemViewType(position)) {
holder.itemTitle.setText(item.getTitle());
holder.itemCheckBox.setChecked(item.isChecked());
} else if (ListItem.TYPE_IMAGE == getItemViewType(position)) {
holder.itemImage.setImageResource(item.getImageId());
}
return convertView;
}
@Override
public int getItemViewType(int position) {
return getItem(position).getType();
}
@Override
public int getViewTypeCount() {
return ListItem.TYPE_CNT;
}
// 用于更新ListView里的item,这里是单个item数据更新,数据更新量小,所以不用notifyDataSetChanged()
void updateView(int itemIndex, String str, boolean isChecked){
int visiblePosition = listView.getFirstVisiblePosition();
View v = listView.getChildAt(itemIndex - visiblePosition);
if (null == v) {
Log.d(TAG, "itemIndex=" + itemIndex + ", visiblePosition=" + visiblePosition);
return;
}
ListItem item = getItem(itemIndex);
item.setTitle(str);
item.setChecked(isChecked);
ViewHolder holder = (ViewHolder)v.getTag();
if (ListItem.TYPE_CHECKBOX == getItemViewType(itemIndex)) {
holder.itemTitle.setText(str);
holder.itemCheckBox.setChecked(isChecked);
}
}
// 这里使用静态类,和ListAdapter脱离关系,以节省内容。使用ViewHolder的目的是为了便于引用List Item的各种View,避免每次都通过findViewById()方法去取的,以提高效率。
static class ViewHolder {
int position = 0; // !!! 这里非常关键,大家可以试试这里不保存item在数据集List<ListItem>里的位置,点击list item的CheckBox后,滚动,大家会发现更新不对的问题。
CheckBox itemCheckBox;
TextView itemTitle;
ImageView itemImage;
}
}
这里直接把数据更新时的解决方法用注释的方法说了出来,大家可以根据注释的内容修改一下代码,就可以看到item更新出错的问题。
备注:Demo下载位置。