在使用ListView时,一般为了性能的提升,都会使用ViewHolder,也就是Item的View实现复用。
现在的问题是,当在ListView的Item中包含CheckBox,并且CheckBox的事件处理监听器是holder.checkbox.setOnCheckedChangeListener()时,会出现第一项开始未选中,当第二项选中时第一项也跟着选中,这显然不是我们想要的结果。
出现这个问题的原因是第一项和第二项用的是同一个Item,当第二项选中时,CheckBox的当前状态为选中,这时setOnCheckedChangeListener里面会改变第一项关联的实体对象的属性(引用类型,变量A、B都引用同一个对象AA,当A把AA的某个属性值修改了,B再次访问时,AA对象的那个属性的值为A引用改后的值),代码如下:
1 | holder.checkbox.setOnCheckedChangeListener( new OnCheckedChangeListener() { |
4 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { |
5 | driver.setSelected(isChecked); |
解决办法:
1、在ListViewAdapter初始化时,将对象中有关CheckBox是否选中的属性存储起来。
1 | selectedMap = new HashMap<Integer, Boolean>(); |
2 | int size = mPersons.size(); |
3 | for ( int i = 0 ; i < size; i++) { |
4 | 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是否选中状态,由之前初始化时保存的对象属性值控制,代码如下:
1 | boolean selected = selectedMap.get(position); |
2 | holder.checkbox.setChecked(selected); |
3、用户点击ListView的Item时,改变CheckBox的状态,代码如下:
1 | convertView.setOnClickListener( new View.OnClickListener() { |
4 | public void onClick(View v) { |
6 | selectedMap.put(position, checkbox.isChecked()); |
7 | driver.setSelected(checkbox.isChecked()); |
数据适配器ListViewAdapter的完整代码:
001 | package com.easipass.cloud.ccp.adapter; |
003 | import java.util.ArrayList; |
004 | import java.util.HashMap; |
006 | import android.content.Context; |
007 | import android.view.LayoutInflater; |
008 | import android.view.View; |
009 | import android.view.ViewGroup; |
010 | import android.widget.BaseAdapter; |
011 | import android.widget.CheckBox; |
012 | import android.widget.Filter; |
013 | import android.widget.Filterable; |
014 | import android.widget.ImageView; |
015 | import android.widget.TextView; |
017 | import com.easipass.R; |
018 | import com.easipass.cloud.ccp.entity.UserInfo; |
025 | public final class UserListViewAdapter extends BaseAdapter implements Filterable { |
026 | private LayoutInflater inflater; |
028 | private MyFilter myFilter; |
030 | private final Object mLock = new Object(); |
032 | private ArrayList<UserInfo> mPersons; |
034 | private ArrayList<UserInfo> mCheckValues; |
036 | public HashMap<Integer, Boolean> selectedMap; |
038 | public UserListViewAdapter(Context context, ArrayList<UserInfo> cms) { |
039 | inflater = LayoutInflater.from(context); |
042 | selectedMap = new HashMap<Integer, Boolean>(); |
043 | int size = mPersons.size(); |
044 | for ( int i = 0 ; i < size; i++) { |
045 | selectedMap.put(i, mPersons.get(i).isSelected()); |
051 | public int getCount() { |
052 | return mPersons.size(); |
056 | public Object getItem( int arg0) { |
057 | return mPersons.get(arg0); |
061 | public long getItemId( int position) { |
066 | public View getView( final int position, View convertView, ViewGroup parent) { |
067 | ViewHolder holder = null ; |
068 | if (convertView == null ) { |
069 | convertView = inflater.inflate(R.layout.ccp_carmanager_lv_item, null ); |
070 | holder = new ViewHolder(); |
071 | holder.text1 = (TextView) convertView.findViewById(R.id.tv_name); |
072 | holder.text2 = (TextView) convertView.findViewById(R.id.tv_phnoe); |
073 | holder.checkbox = (CheckBox) convertView.findViewById(R.id.checkbox); |
074 | holder.imageView = (ImageView) convertView.findViewById(R.id.iv_icon); |
076 | convertView.setTag(holder); |
078 | holder = (ViewHolder) convertView.getTag(); |
081 | final UserInfo driver = mPersons.get(position); |
083 | holder.text1.setText(driver.getName()); |
085 | holder.text2.setText(driver.getPhoneNumber()); |
087 | holder.imageView.setBackgroundResource(Integer.valueOf(driver.getIconUrl())); |
088 | holder.checkbox.setVisibility(View.VISIBLE); |
090 | boolean selected = selectedMap.get(position); |
091 | holder.checkbox.setChecked(selected); |
093 | final CheckBox checkbox = holder.checkbox; |
094 | convertView.setOnClickListener( new View.OnClickListener() { |
097 | public void onClick(View v) { |
099 | selectedMap.put(position, checkbox.isChecked()); |
100 | driver.setSelected(checkbox.isChecked()); |
105 | convertView.setClickable( false ); |
112 | public Filter getFilter() { |
113 | if (myFilter == null ) { |
114 | myFilter = new MyFilter(); |
119 | class MyFilter extends Filter { |
122 | protected FilterResults performFiltering(CharSequence prefix) { |
123 | FilterResults results = new FilterResults(); |
124 | if (mCheckValues == null ) { |
125 | synchronized (mLock) { |
126 | mCheckValues = new ArrayList<UserInfo>(mPersons); |
130 | if (prefix == null || prefix.length() == 0 ) { |
131 | synchronized (mLock) { |
132 | ArrayList<UserInfo> list = new ArrayList<UserInfo>(mCheckValues); |
133 | results.values = list; |
134 | results.count = list.size(); |
137 | String prefixString = prefix.toString().toLowerCase(); |
138 | final ArrayList<UserInfo> values = mCheckValues; |
139 | final int count = values.size(); |
141 | final ArrayList<UserInfo> newValues = new ArrayList<UserInfo>(count); |
142 | for ( int i = 0 ; i < count; i++) { |
143 | final UserInfo value = (UserInfo) values.get(i); |
144 | if (value.getName().contains(prefixString)) { |
145 | newValues.add(value); |
149 | results.values = newValues; |
150 | results.count = newValues.size(); |
156 | @SuppressWarnings ( "unchecked" ) |
158 | protected void publishResults(CharSequence constraint, FilterResults results) { |
159 | mPersons = (ArrayList<UserInfo>) results.values; |
160 | if (results.count > 0 ) { |
161 | notifyDataSetChanged(); |
163 | notifyDataSetInvalidated(); |
168 | static class ViewHolder { |
169 | public TextView text1; |
171 | public TextView text2; |
173 | public ImageView imageView; |
175 | public CheckBox checkbox; |
Activity中onCreate()里的写法:
1 | mSearchToolbar = (SearchToolbar) this .findViewById(R.id.top_search_toolbar); |
2 | mListView = (ListView) this .findViewById(R.id.listview); |
4 | mDriverListAdapter = new UserListViewAdapter( this , driverList); |
5 | mListView.setAdapter(mDriverListAdapter); |
7 | mSearchToolbar.setFilter(mDriverListAdapter.getFilter()); |
SearchToolbar类的代码:
01 | package com.easipass.custom.view; |
02 | import android.content.Context; |
03 | import android.text.Editable; |
04 | import android.text.TextWatcher; |
05 | import android.util.AttributeSet; |
06 | import android.view.LayoutInflater; |
07 | import android.view.View; |
08 | import android.widget.AutoCompleteTextView; |
09 | import android.widget.FrameLayout; |
10 | import android.widget.ImageView; |
11 | import android.widget.RelativeLayout; |
19 | public class SearchToolbar extends FrameLayout { |
21 | private RelativeLayout topSearchToolbar; |
26 | private AutoCompleteTextView autoSearch; |
31 | private ImageView btnClearSearch; |
33 | public SearchToolbar(Context context) { |
38 | public SearchToolbar(Context context, AttributeSet attrs) { |
39 | super (context, attrs); |
43 | private void setupViews() { |
44 | final LayoutInflater mLayoutInflater = LayoutInflater.from(getContext()); |
45 | topSearchToolbar = (RelativeLayout) mLayoutInflater.inflate(R.layout.top_search_toolbar, null ); |
46 | addView(topSearchToolbar); |
48 | btnClearSearch = (ImageView) topSearchToolbar.findViewById(R.id.iv_search_clear); |
49 | autoSearch = (AutoCompleteTextView) topSearchToolbar.findViewById(R.id.auto_search); |
52 | public void setFilter( final android.widget.Filter filter) { |
53 | autoSearch.addTextChangedListener( new TextWatcher() { |
56 | public void onTextChanged(CharSequence s, int start, int before, int count) { |
57 | String filterWord = autoSearch.getText().toString().trim(); |
58 | filter.filter(filterWord); |
62 | public void beforeTextChanged(CharSequence s, int start, int count, int after) { |
68 | public void afterTextChanged(Editable s) { |
74 | btnClearSearch.setOnClickListener( new View.OnClickListener() { |
77 | public void onClick(View v) { |
78 | autoSearch.setText( null ); |
top_search_toolbar.xml文件:
01 | <? xml version = "1.0" encoding = "utf-8" ?> |
02 | < RelativeLayout xmlns:android = "http://schemas.android.com/apk/res/android" |
03 | android:id = "@+id/top_search_bar" |
04 | android:layout_width = "match_parent" |
05 | android:layout_height = "45dip" |
06 | android:background = "@drawable/search_bar_bg" |
07 | android:visibility = "visible" > |
09 | android:id = "@+id/auto_search" |
10 | android:layout_width = "fill_parent" |
11 | android:layout_height = "wrap_content" |
12 | android:layout_centerVertical = "true" |
13 | android:layout_marginLeft = "5dip" |
14 | android:layout_marginRight = "5dip" |
15 | android:background = "@drawable/search_bar_edit_normal" |
16 | android:completionThreshold = "1" |
17 | android:drawableLeft = "@drawable/search_bar_icon_normal" |
18 | android:dropDownHorizontalOffset = "30dip" |
19 | android:dropDownVerticalOffset = "9dip" |
20 | android:dropDownWidth = "210dip" |
21 | android:singleLine = "true" |
22 | android:textSize = "15sp" /> |
25 | android:id = "@+id/iv_search_clear" |
26 | android:layout_width = "wrap_content" |
27 | android:layout_height = "wrap_content" |
28 | android:layout_alignParentRight = "true" |
29 | android:layout_centerVertical = "true" |
30 | android:layout_marginRight = "12dip" |
31 | android:layout_marginTop = "-1dip" |
32 | android:background = "@drawable/btn_search_clear_selector" /> |