完美解决ListView和CheckBox焦点冲突及复用时CheckBox错位等一系列问题
Posted zhangjinhuang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了完美解决ListView和CheckBox焦点冲突及复用时CheckBox错位等一系列问题相关的知识,希望对你有一定的参考价值。
前段时间项目进行版本升级的时候遇到这么一个需求:当点击界面右上角的“编辑”按钮时,会在列表的每个item的左边显示一个用于选中进行删除的CheckBox,刚开始的时候觉得非常容易,但是越往下问题也越多,并且由于ListView具有下拉刷新和上拉加载的功能,因此实现起来也更加的困难,比如处于编辑状态时,下拉刷新新增item时也要保持已选中的item及上拉加载更多时也要保持已选中的item,同时对下拉刷新和上拉加载新增的item也要具有可选操作。因此借这篇博客向大家分享一下我的实现方式,首先让大家看一下效果图:
由于布局中含有CheckBox,因此首先要做的是解决焦点问题,在这里就需要用到android中的一个descendantFocusability属性,该属性值也有如下三种:
beforeDescendants:表示ViewGroup会优先其子类控件而获取到焦点;
afterDescendants:表示ViewGroup只有当其子类控件不需要获取焦点时才获取焦点;
blocksDescendants:表示ViewGroup会覆盖子类控件而直接获得焦点。
通常我们用到的是第三种,即在item布局的根布局中添加android:descendantFocusability = “blocksDescendants”,通过此种方式即可解决ListView的item布局中含有CheckBox时所产生的焦点冲突问题。
焦点冲突问题解决了,接下来需要实现的是如何处理CheckBox的状态,即默认状态为未选中,其次当选中的时候不管是下拉刷新增加新的item还是上拉加载出更多的item都需要保持原来选中的状态,另外新增加的item和加载的item都需要像其它item一样可以对CheckBox进行操作,最后,由于在使用ListView时为了减少对内存的消耗,因此在自定义适配器的时候为了优化ListView都会复用View,这样的话就会造成前面选中CheckBox时后面复用的item的CheckBox也会被选中的问题。
为了满足这一系列的要求,首先需要定义一个用来保存选中位置和对应状态的Map集合并且在ListView的Adapter的构造函数中对其进行初始化,代码如下所示:
/**
* 用来保存选中状态和对应的位置,用于解决item的复用问题
*/
public static Map<Integer, Boolean> isSelected;
对Map集合进行初始化,代码如下:
/**
* 初始选中状态
*
* @param size 表示数据的长度,是为了解决下拉刷新和上拉加载时产生新的item时能够都有默认初始值
*/
private void initSelected(int size)
//判断isSelected是否已经存在
if (isSelected == null)
isSelected = new HashMap<>();
for (int i = 0; i < size; i++)
isSelected.put(i, false);
对Map集合初始完毕之后,就可以在getView()方法中对CheckBox进行状态的设置,如CheckBox显示时默认为未选中状态,代码如下:
//判断是否处于编辑状态
if (isVisible)
holder.llayout_parent.setVisibility(View.VISIBLE);
//设置CheckBox默认状态为未选中
holder.cb_checkbox.setChecked(isSelected.get(position));
else //如果CheckBox为不可见,则设置CheckBox为未选中状态
holder.llayout_parent.setVisibility(View.GONE);
holder.cb_checkbox.setChecked(false);
至此,
CheckBox
的默认状态就初始完了,接下来要做的是定义一个用来保存之前选中状态位置的
List
集合,用于加载更多数据后恢复先前已选中的位置,代码如下:
// 用来保存之前选中状态的位置,用于下拉刷新和上拉加载更多数据时恢复已选中的位置
public static List<Integer> hasSelected = new ArrayList<>();
同时也需要在初始化用于设置CheckBox默认状态的Map集合中进行初始化List集合,添加后的代码如下所示:
/**
* 初始选中状态
*
* @param size
*/
private void initSelected(int size)
//判断isSelected是否已经存在
if (isSelected == null)
isSelected = new HashMap<>();
for (int i = 0; i < size; i++)
isSelected.put(i, false);
else//此部分适用于具有上拉加载功能的ListView
for (int i = 0; i < size; i++)
isSelected.put(i,false);
//遍历加载之前所保存的选中的位置
int length = hasSelected.size();
for (int j = 0; j < length; j++)
if(i==hasSelected.get(j))
isSelected.put(i,true);
到这里,在Adapter中对CheckBox的一系列操作就结束了,ListView适配器的完整代码如下所示:
package abner.listview.with.checkbox;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.CheckBox;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class CollectionAdapter extends BaseAdapter
/**
* 用来保存选中状态和对应的位置,用于解决item的复用问题
*/
public static Map<Integer, Boolean> isSelected;
/**
* 用来保存之前选中状态的位置,用于加载更多数据时恢复已选中的位置
*/
public static List<Integer> hasSelected = new ArrayList<>();
private Context context;
private List<Collection> collectionList;
private boolean isVisible = false;
public CollectionAdapter(Context context, List<Collection> messageList)
this.context = context;
this.collectionList = messageList;
int size = messageList.size();
initSelected(size);
public void setList(List<Collection> messageList)
this.collectionList = messageList;
int size = messageList.size();
initSelected(size);
/**
* 初始选中状态
*
* @param size
*/
private void initSelected(int size)
//判断isSelected是否已经存在
if (isSelected == null)
isSelected = new HashMap<>();
for (int i = 0; i < size; i++)
isSelected.put(i, false);
else//此部分适用于具有上拉加载功能的ListView
for (int i = 0; i < size; i++)
isSelected.put(i,false);
//遍历加载之前所保存的选中的位置
int length = hasSelected.size();
for (int j = 0; j < length; j++)
if(i==hasSelected.get(j))
isSelected.put(i,true);
public void setVisible(boolean visible)
this.isVisible = visible;
public boolean isVisible()
return isVisible;
@Override
public int getCount()
return collectionList.size();
@Override
public Object getItem(int position)
return collectionList.get(position);
@Override
public long getItemId(int position)
return position;
@Override
public View getView(int position, View convertView, ViewGroup parent)
ViewHolder holder;
if (convertView == null)
holder = new ViewHolder();
convertView = View.inflate(context, R.layout.item_collection, null);
holder.tv_title = (TextView) convertView.findViewById(R.id.tv_title);
holder.tv_description = (TextView) convertView.findViewById(R.id.tv_description);
holder.llayout_parent = (LinearLayout) convertView.findViewById(R.id.llayout_parent);
holder.cb_checkbox = (CheckBox) convertView.findViewById(R.id.cb_checkbox);
convertView.setTag(holder);
else
holder = (ViewHolder) convertView.getTag();
final Collection collection = collectionList.get(position);
holder.tv_title.setText(collection.getTitle());
holder.tv_description.setText(collection.getDescription());
//判断是否处于编辑状态
if (isVisible)
holder.llayout_parent.setVisibility(View.VISIBLE);
holder.cb_checkbox.setChecked(isSelected.get(position));
else
holder.llayout_parent.setVisibility(View.GONE);
holder.cb_checkbox.setChecked(false);
return convertView;
class ViewHolder
TextView tv_title;
TextView tv_description;
LinearLayout llayout_parent;
CheckBox cb_checkbox;
接下来,就来讲解如何在点击编辑按钮时让CheckBox显示并进行选择、全选、反选、删除等功能的实现,首先就是在编辑按钮的点击事件中判断Adapter适配器中的isVisible的值是否为true,如果是则设置CheckBox为不可见,反之为可见,关键代码如下所示:
if (!adapter.isVisible())
btn_edit.setText("取消");
//显示CheckBox
adapter.setVisible(true);
adapter.notifyDataSetChanged();
else
btn_edit.setText("编辑");
//隐藏CheckBox
adapter.setVisible(false);
至于对CheckBox的选择、全选、反选、删除的实现则需要先在ListView的onItemClick事件中对CheckBox的状态进行一些判断和值的处理,如:在编辑状态下,点击item的时候需要对CheckBox的状态进行切换(即CheckBox为选中时需要切换到未选中状态,反之亦然),其次是需要将点击的item的位置及对应的CheckBox的状态值保存到Adapter适配器中定义好的Map<Integer,Boolean>集合中,最后需要对在Adapter适配器定义好的用来保存点击位置的List集合进行判断,判断点击的位置是否已经存在,如果已经存在则移除,否则添加至List集合中,主要代码如下:
//判断CheckBox是否处于可见状态
if (adapter.isVisible())
CollectionAdapter.ViewHolder holder = (CollectionAdapter.ViewHolder) view.getTag();
//每次点击item都对checkbox的状态进行改变
holder.cb_checkbox.toggle();
//同时将CheckBox的状态保存到HashMap中,其中key为点击的位置,value为状态
adapter.isSelected.put(position, holder.cb_checkbox.isChecked());
//判断是否已经存在,如果已经存在,则移除,否则添加
if (adapter.hasSelected.contains(position))
adapter.hasSelected.remove(position);
else
adapter.hasSelected.add(position);
再接下来的就是对CheckBox的全选和反选功能的实现了,对于全选,只需对数据源进行遍历,然后在其中对在Adapter适配器中定义好的Map<Integer,Boolean> isSelected和List<Integer> hasSelected集合重新进行赋值即可,主要代码如下所示:
/**
* 全选
*/
public void selectAll()
for (int i = 0; i < collectionList.size(); i++)
adapter.isSelected.put(i, true);
adapter.hasSelected.add(i);
collectionList.get(i).setSelect(true);
adapter.notifyDataSetChanged();
/**
* 反选
*/
public void cancelAll()
for (int i = 0; i < collectionList.size(); i++)
adapter.isSelected.put(i, false);
adapter.hasSelected.clear();
collectionList.get(i).setSelect(false);
adapter.notifyDataSetChanged();
最后,就是对CheckBox的删除功能的实现了,由于数据源都是本地数据,因此实现起来反而比较的麻烦点,如果是服务器的数据,如果需要实现对CheckBox的删除,只需将需要删除的id等上传到服务器,由后台进行删除操作,对于本地数据的删除,首先需要判断是否有选择要删除的数据,如果有则对数据源进行遍历并判断是哪个对象被选中,然后为其设置一个boolean类型的状态值,最后就可以通过Iterator来删除List集合中某个对象了,主要代码如下所示:
/**
* 删除所选数据
*/
public void delete()
//如果有选择要删除的内容才进行删除,否则提示用户还没有选择要删除的内容
if(adapter.hasSelected.size()>0)
//此处删除的是自己定义好的本地数据
int size = collectionList.size();
for (int i = 0; i < size; i++)
if (adapter.isSelected.get(i))
collectionList.get(i).setSelect(true);
else
collectionList.get(i).setSelect(false);
//此处借助Iterator来删除List集合中的某个对象
Iterator<Collection> iter = collectionList.iterator();
while (iter.hasNext())
Collection massage = iter.next();
//如果是选中状态,则将其从集合中移除
if (massage.isSelect())
iter.remove();
//删除完后,重置CheckBox的状态
resetState();
else
Toast.makeText(this,"还没有选择要删除的内容",Toast.LENGTH_SHORT).show();
至于这里为什么要借助Iterator来删除List集合中的对象可以参考这篇博客:http://blog.csdn.net/wangpeng047/article/details/7590555
到此,ListView和CheckBox的焦点冲突及CheckBox复用时产生的问题就告一段落了,以后再也不用郁闷在Adapter适配器中复用View时为什么上面选中的CheckBox在往下拉时下面item中的CheckBox也会选中了。
Activity的完整代码如下所示:
package abner.listview.with.checkbox;
import android.app.Activity;
import android.os.Bundle;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.ListView;
import android.widget.PopupWindow;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class MainActivity extends Activity
private ListView lv_content;
private CollectionAdapter adapter;
private Button btn_edit,btn_back;
private List<Collection> collectionList;
private int lastVisibleItem;
private int totalItemCount;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
initViews();
initEvents();
private void initEvents()
btn_edit.setOnClickListener(new OnClickListener()
@Override
public void onClick(View v)
if (!adapter.isVisible())
btn_edit.setText("取消");
btn_back.setVisibility(View.VISIBLE);
adapter.setVisible(true);
showDeletePopupWindow();
adapter.notifyDataSetChanged();
else
btn_back.setVisibility(View.GONE);
btn_edit.setText("编辑");
//处理组件的显示状态
handleComponentState();
);
lv_content.setOnItemClickListener(new AdapterView.OnItemClickListener()
@Override
public void onItemClick(AdapterView<?> arg0, View view, int position, long id)
//判断CheckBox是否处于可见状态
if (adapter.isVisible())
CollectionAdapter.ViewHolder holder = (CollectionAdapter.ViewHolder) view.getTag();
//每次点击item都对checkbox的状态进行改变
holder.cb_checkbox.toggle();
//同时将CheckBox的状态保存到HashMap中,其中key为点击的位置,value为状态
adapter.isSelected.put(position, holder.cb_checkbox.isChecked());
//判断是否已经存在,如果已经,则移除,否则添加
if (adapter.hasSelected.contains(position))
adapter.hasSelected.remove(position);
else
adapter.hasSelected.add(position);
);
btn_back.setOnClickListener(new OnClickListener()
@Override
public void onClick(View v)
//处理组件的显示状态
handleComponentState();
);
lv_content.setOnScrollListener(new AbsListView.OnScrollListener()
@Override
public void onScrollStateChanged(AbsListView view, int scrollState)
if (totalItemCount == lastVisibleItem && scrollState == SCROLL_STATE_IDLE)
addLoadMore();
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
lastVisibleItem = firstVisibleItem + visibleItemCount;
MainActivity.this.totalItemCount = totalItemCount;
);
/**
* 处理组件的显示状态
*/
private void handleComponentState()
//隐藏删除的PopupWindow
dismissDeletePopupWindow();
//返回时重置CheckBox的状态
resetState();
btn_edit.setText("编辑");
btn_back.setVisibility(View.GONE);
//设置CheckBox不可见
adapter.setVisible(false);
adapter.notifyDataSetChanged();
private PopupWindow pw_delete;
private TextView tv_delete;
private TextView tv_selectAll;
/**
* 弹出删除的PopupWindow
*/
private void showDeletePopupWindow()
//加载PopupWindow的布局文件
View view = LayoutInflater.from(this).inflate(R.layout.delete_popupwindow, null);
tv_delete = (TextView) view.findViewById(R.id.tv_delete);
tv_selectAll = (TextView) view.findViewById(R.id.tv_selectAll);
//实例化PopupWindow
pw_delete = new PopupWindow(view, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
//设置PopupWindow弹出时的动画
pw_delete.setAnimationStyle(R.style.PopupWindowAnimation);
//PopupWindow的显示问题
pw_delete.showAtLocation(findViewById(R.id.rlayout_root), Gravity.BOTTOM, 0, 0);
//为删除和全选绑定事件
tv_delete.setOnClickListener(listener);
tv_selectAll.setOnClickListener(listener);
private OnClickListener listener = new OnClickListener()
@Override
public void onClick(View v)
switch (v.getId())
case R.id.tv_delete:
delete();
break;
case R.id.tv_selectAll:
if ("全选".equals(tv_selectAll.getText()))
selectAll();
tv_selectAll.setText("反选");
else
cancelAll();
tv_selectAll.setText("全选");
break;
;
/**
* 隐藏删除的PopupWindow
*/
private void dismissDeletePopupWindow()
if(pw_delete!=null&&pw_delete.isShowing())
pw_delete.dismiss();
pw_delete = null;
/**
* 加载更多
*/
private void addLoadMore()
for (int i = 0; i < 5; i++)
Collection collection = new Collection("这是加载的收藏的标题"+i,"这是加载的收藏的描述"+i);
collectionList.add(collection);
adapter.setList(collectionList);
adapter.notifyDataSetChanged();
/**
* 重置CheckBox的状态
*/
private void resetState()
for (int i = 0; i < collectionList.size(); i++)
adapter.isSelected.put(i, false);
adapter.hasSelected.clear();
tv_selectAll.setText("全选");
adapter.notifyDataSetChanged();
/**
* 初始化组件
*/
public void initViews()
lv_content = (ListView) findViewById(R.id.list_view);
btn_edit = (Button) findViewById(R.id.btn_edit);
btn_back = (Button) findViewById(R.id.btn_back);
adapter = new CollectionAdapter(this, initDatas());
lv_content.setAdapter(adapter);
/**
* 初始化数据
*
* @return
*/
public List<Collection> initDatas()
collectionList = new ArrayList<>();
for (int i = 0; i < 10; i++)
Collection mas = new Collection("这是收藏的标题"+i,"这是收藏的描述"+i);
collectionList.add(mas);
return collectionList;
/**
* 删除所选数据
*/
public void delete()
//如果有选择要删除的内容才进行删除,否则提示用户还没有选择要删除的内容
if(adapter.hasSelected.size()>0)
//此处删除的是自己定义好的本地数据
int size = collectionList.size();
for (int i = 0; i < size; i++)
if (adapter.isSelected.get(i))
collectionList.get(i).setSelect(true);
else
collectionList.get(i).setSelect(false);
//此处借助Iterator来删除List集合中的某个对象
Iterator<Collection> iter = collectionList.iterator();
while (iter.hasNext())
Collection massage = iter.next();
//如果是选中状态,则将其从集合中移除
if (massage.isSelect())
iter.remove();
//删除完后,重置CheckBox的状态
resetState();
else
Toast.makeText(this,"还没有选择要删除的内容",Toast.LENGTH_SHORT).show();
/**
* 全选
*/
public void selectAll()
for (int i = 0; i < collectionList.size(); i++)
adapter.isSelected.put(i, true);
adapter.hasSelected.add(i);
collectionList.get(i).setSelect(true);
adapter.notifyDataSetChanged();
/**
* 取消全选
*/
public void cancelAll()
for (int i = 0; i < collectionList.size(); i++)
adapter.isSelected.put(i, false);
adapter.hasSelected.clear();
collectionList.get(i).setSelect(false);
adapter.notifyDataSetChanged();
以上是关于完美解决ListView和CheckBox焦点冲突及复用时CheckBox错位等一系列问题的主要内容,如果未能解决你的问题,请参考以下文章
Android学习笔记之ListView与Item的焦点冲突处理