ListView里面如果有CheckBox跟RadioButton的话,而且Adapter复用了convertView的话,基本上是会100%出现滑动错乱的,比如像下面这样的
出现这个问题的根本原因还是在convertView复用的时候没有对CheckBox做处理,我们知道,假设手机屏幕一屏能显示7个item,则刚开始会一次性创建7个convertView,当屏幕往上滑的时候,第1个item不可见时,系统会将它缓存起来复用给后面的item,所以在滑动到第8个item的时候,getView()方法中的convertView就是第1个item的。重用convertView可以避免大量重复的inflate跟findViewById,会使ListView的性能大大提升,但是带来的问题就是,第8个item的convertView其实是第1个复用下来的,如果我们在第1个item上做了操作,而在第8个item上没有刷新数据,就会导致上面的错乱问题。
解决这个问题有两种办法:
在Adapter中使用ArrayList保存所有CheckBox的选中状态,然后在OnCheckedChangeListener中监听CheckBox的状态,对状态进行相应的保存,代码很简单,相信大家看看就明白了,直接上代码
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071public class ListViewAdapter1 extends BaseAdapter {private List<String> cities;private LayoutInflater mInflater;// 创建一个List用于保存CheckBox的选中状态private List<Boolean> checks = new ArrayList<>();public ListViewAdapter1(Context context, List<String> cities) {this.cities = cities;mInflater = LayoutInflater.from(context);for (int i = 0; i < cities.size(); i++) {// 刚开始,所有的CheckBox都是未选中的checks.add(false);}}public int getCount() {return cities.size();}public Object getItem(int position) {return cities.get(position);}public long getItemId(int position) {return position;}public View getView(final int position, View convertView, ViewGroup parent) {ViewHolder holder = null;if (convertView == null) {holder = new ViewHolder();convertView = mInflater.inflate(R.layout.listview_item, parent, false);holder.cbListView = (CheckBox) convertView.findViewById(R.id.cb_listView);holder.tvListView = (TextView) convertView.findViewById(R.id.tv_listView);convertView.setTag(holder);} else {holder = (ViewHolder) convertView.getTag();}holder.tvListView.setText(cities.get(position));holder.cbListView.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {if (isChecked) {// 当CheckBox被选中,将相应position的值设置为truechecks.set(position, true);} else {// 当CheckBox取消选中,将对应position的值设置为falsechecks.set(position,false);}}});// 根据相应位置的checks值设置CheckBox的选中状态holder.cbListView.setChecked(checks.get(position));return convertView;}class ViewHolder {CheckBox cbListView;TextView tvListView;}}第二种方法原理跟第一种一样,也是监听CheckBox的选中状态然后进行保存,只不错用的是
Map<Integer,Boolean>
保存,下面是代码1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768public class ListViewAdapter extends BaseAdapter {private List<String> cities;private LayoutInflater mInflater;private Map<Integer,Boolean> checkBoxState = new HashMap<>();public ListViewAdapter(Context context, List<String> cities) {this.cities = cities;mInflater = LayoutInflater.from(context);}public int getCount() {return cities.size();}public Object getItem(int position) {return cities.get(position);}public long getItemId(int position) {return position;}public View getView(final int position, View convertView, ViewGroup parent) {ViewHolder holder = null;if (convertView == null){holder = new ViewHolder();convertView = mInflater.inflate(R.layout.listview_item,parent,false);holder.cbListView = (CheckBox) convertView.findViewById(R.id.cb_listView);holder.tvListView = (TextView) convertView.findViewById(R.id.tv_listView);convertView.setTag(holder);} else {holder = (ViewHolder) convertView.getTag();}holder.tvListView.setText(cities.get(position));holder.cbListView.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {if (isChecked){// 若CheckBox为选中状态,保存当前位置的选中信息checkBoxState.put(position,true);} else {// 若CheckBox取消选中,则将其从HashMap中移除checkBoxState.remove(position);}}});if (checkBoxState.size() != 0 && checkBoxState.containsKey(position)){holder.cbListView.setChecked(true);} else {holder.cbListView.setChecked(false);}return convertView;}class ViewHolder{CheckBox cbListView;TextView tvListView;}}
处理之后就不会出现滑动错乱了
以上就是解决这个问题的两种方式,此方法同样也适用于RecyclerView。
其实在这里还会有另一个隐藏的问题,那就是如果ListView中包含CheckBox跟Button等,则ListView默认会失去焦点,这个时候设置了setOnItemClickListener()
即item的点击监听是不起作用的,点击ListView的item是不响应的,解决这个问题的办法就是在item的根布局中设置descendantFocusability属性,比如
|
|
该属性是当一个为view获取焦点时,定义viewGroup和其子控件两者之间的关系。
属性的值有三种:
- beforeDescendants:viewgroup会优先其子类控件而获取到焦点
- afterDescendants:viewgroup只有当其子类控件不需要获取焦点时才获取焦点
- blocksDescendants:viewgroup会覆盖子类控件而直接获得焦点