前段时间项目进行版本升级的时候遇到这么一个需求:当点击界面右上角的“编辑”按钮时,会在列表的每个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(); } } }