在使用ListView时,一般为了性能的提升,都会使用ViewHolder,也就是Item的View实现复用。
现在的问题是,当在ListView的Item中包含CheckBox,并且CheckBox的事件处理监听器是holder.checkbox.setOnCheckedChangeListener()时,会出现第一项开始未选中,当第二项选中时第一项也跟着选中,这显然不是我们想要的结果。
出现这个问题的原因是第一项和第二项用的是同一个Item,当第二项选中时,CheckBox的当前状态为选中,这时setOnCheckedChangeListener里面会改变第一项关联的实体对象的属性(引用类型,变量A、B都引用同一个对象AA,当A把AA的某个属性值修改了,B再次访问时,AA对象的那个属性的值为A引用改后的值),代码如下:
holder.checkbox.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { driver.setSelected(isChecked); } });
解决办法:
1、在ListViewAdapter初始化时,将对象中有关CheckBox是否选中的属性存储起来。
selectedMap = new HashMap<Integer, Boolean>(); int size = mPersons.size(); for (int i = 0; i < size; i++) { selectedMap.put(i, mPersons.get(i).isSelected()); }
2、去掉CheckBox的holder.checkbox.setOnCheckedChangeListener(){}事件监听器
3、在Adapter里的 public View getView(final int position, View convertView, ViewGroup parent){}方法体里面,当前的CheckBox是否选中状态,由之前初始化时保存的对象属性值控制,代码如下:
boolean selected = selectedMap.get(position); holder.checkbox.setChecked(selected);
3、用户点击ListView的Item时,改变CheckBox的状态,代码如下:
convertView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { checkbox.toggle(); selectedMap.put(position, checkbox.isChecked()); driver.setSelected(checkbox.isChecked()); } });
数据适配器ListViewAdapter的完整代码:
package com.easipass.cloud.ccp.adapter; import java.util.ArrayList; import java.util.HashMap; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.CheckBox; import android.widget.Filter; import android.widget.Filterable; import android.widget.ImageView; import android.widget.TextView; import com.easipass.R; import com.easipass.cloud.ccp.entity.UserInfo; /** * 用户列表数据适配器 * * @author android_ls */ public final class UserListViewAdapter extends BaseAdapter implements Filterable { private LayoutInflater inflater; private MyFilter myFilter; private final Object mLock = new Object(); private ArrayList<UserInfo> mPersons; private ArrayList<UserInfo> mCheckValues; public HashMap<Integer, Boolean> selectedMap; public UserListViewAdapter(Context context, ArrayList<UserInfo> cms) { inflater = LayoutInflater.from(context); mPersons = cms; selectedMap = new HashMap<Integer, Boolean>(); int size = mPersons.size(); for (int i = 0; i < size; i++) { selectedMap.put(i, mPersons.get(i).isSelected()); } } @Override public int getCount() { return mPersons.size(); } @Override public Object getItem(int arg0) { return mPersons.get(arg0); } @Override public long getItemId(int position) { return position; } @Override public View getView(final int position, View convertView, ViewGroup parent) { ViewHolder holder = null; if (convertView == null) { convertView = inflater.inflate(R.layout.ccp_carmanager_lv_item, null); holder = new ViewHolder(); holder.text1 = (TextView) convertView.findViewById(R.id.tv_name); holder.text2 = (TextView) convertView.findViewById(R.id.tv_phnoe); holder.checkbox = (CheckBox) convertView.findViewById(R.id.checkbox); holder.imageView = (ImageView) convertView.findViewById(R.id.iv_icon); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } final UserInfo driver = mPersons.get(position); holder.text1.setText(driver.getName()); holder.text2.setText(driver.getPhoneNumber()); // TODO 测试 holder.imageView.setBackgroundResource(Integer.valueOf(driver.getIconUrl())); holder.checkbox.setVisibility(View.VISIBLE); boolean selected = selectedMap.get(position); holder.checkbox.setChecked(selected); final CheckBox checkbox = holder.checkbox; convertView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { checkbox.toggle(); selectedMap.put(position, checkbox.isChecked()); driver.setSelected(checkbox.isChecked()); } }); if (selected) { convertView.setClickable(false); } return convertView; } @Override public Filter getFilter() { if (myFilter == null) { myFilter = new MyFilter(); } return myFilter; } class MyFilter extends Filter { @Override protected FilterResults performFiltering(CharSequence prefix) { FilterResults results = new FilterResults(); if (mCheckValues == null) { synchronized (mLock) { mCheckValues = new ArrayList<UserInfo>(mPersons); } } if (prefix == null || prefix.length() == 0) { synchronized (mLock) { ArrayList<UserInfo> list = new ArrayList<UserInfo>(mCheckValues); results.values = list; results.count = list.size(); } } else { String prefixString = prefix.toString().toLowerCase(); final ArrayList<UserInfo> values = mCheckValues; final int count = values.size(); final ArrayList<UserInfo> newValues = new ArrayList<UserInfo>(count); for (int i = 0; i < count; i++) { final UserInfo value = (UserInfo) values.get(i); if (value.getName().contains(prefixString)) { newValues.add(value); } } results.values = newValues; results.count = newValues.size(); } return results; } @SuppressWarnings("unchecked") @Override protected void publishResults(CharSequence constraint, FilterResults results) { mPersons = (ArrayList<UserInfo>) results.values; if (results.count > 0) { notifyDataSetChanged(); } else { notifyDataSetInvalidated(); } } } static class ViewHolder { public TextView text1; public TextView text2; public ImageView imageView; public CheckBox checkbox; } }
Activity中onCreate()里的写法:
mSearchToolbar = (SearchToolbar) this.findViewById(R.id.top_search_toolbar); mListView = (ListView) this.findViewById(R.id.listview); mDriverListAdapter = new UserListViewAdapter(this, driverList); mListView.setAdapter(mDriverListAdapter); mSearchToolbar.setFilter(mDriverListAdapter.getFilter());
SearchToolbar类的代码:
package com.easipass.custom.view; import android.content.Context; import android.text.Editable; import android.text.TextWatcher; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.widget.AutoCompleteTextView; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.RelativeLayout; import com.easipass.R; /** * 功能描述:自定义搜索框组件 * @author android_ls */ public class SearchToolbar extends FrameLayout { private RelativeLayout topSearchToolbar; /** * 顶部自动补全文本输入框 */ private AutoCompleteTextView autoSearch; /** * 清除搜索结果按钮 */ private ImageView btnClearSearch; public SearchToolbar(Context context) { super(context); setupViews(); } public SearchToolbar(Context context, AttributeSet attrs) { super(context, attrs); setupViews(); } private void setupViews() { final LayoutInflater mLayoutInflater = LayoutInflater.from(getContext()); topSearchToolbar = (RelativeLayout) mLayoutInflater.inflate(R.layout.top_search_toolbar, null); addView(topSearchToolbar); btnClearSearch = (ImageView) topSearchToolbar.findViewById(R.id.iv_search_clear); autoSearch = (AutoCompleteTextView) topSearchToolbar.findViewById(R.id.auto_search); } public void setFilter(final android.widget.Filter filter) { autoSearch.addTextChangedListener(new TextWatcher() { @Override public void onTextChanged(CharSequence s, int start, int before, int count) { String filterWord = autoSearch.getText().toString().trim(); filter.filter(filterWord); } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { // TODO Auto-generated method stub } @Override public void afterTextChanged(Editable s) { // TODO Auto-generated method stub } }); btnClearSearch.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { autoSearch.setText(null); } }); } }
top_search_toolbar.xml文件:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/top_search_bar" android:layout_width="match_parent" android:layout_height="45dip" android:background="@drawable/search_bar_bg" android:visibility="visible" > <AutoCompleteTextView android:id="@+id/auto_search" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="5dip" android:layout_marginRight="5dip" android:background="@drawable/search_bar_edit_normal" android:completionThreshold="1" android:drawableLeft="@drawable/search_bar_icon_normal" android:dropDownHorizontalOffset="30dip" android:dropDownVerticalOffset="9dip" android:dropDownWidth="210dip" android:singleLine="true" android:textSize="15sp" /> <ImageView android:id="@+id/iv_search_clear" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:layout_marginRight="12dip" android:layout_marginTop="-1dip" android:background="@drawable/btn_search_clear_selector" /> </RelativeLayout>