我是靠谱客的博主 危机鱼,最近开发中收集的这篇文章主要介绍完美解决ListView和CheckBox焦点冲突及复用时CheckBox错位等一系列问题,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

前段时间项目进行版本升级的时候遇到这么一个需求:当点击界面右上角的“编辑”按钮时,会在列表的每个item的左边显示一个用于选中进行删除的CheckBox,刚开始的时候觉得非常容易,但是越往下问题也越多,并且由于ListView具有下拉刷新和上拉加载的功能,因此实现起来也更加的困难,比如处于编辑状态时,下拉刷新新增item时也要保持已选中的item及上拉加载更多时也要保持已选中的item,同时对下拉刷新和上拉加载新增的item也要具有可选操作。因此借这篇博客向大家分享一下我的实现方式,首先让大家看一下效果图:

 

由于布局中含有CheckBox,因此首先要做的是解决焦点问题,在这里就需要用到android中的一个descendantFocusability属性,该属性值也有如下三种:

beforeDescendants:表示ViewGroup会优先其子类控件而获取到焦点;

afterDescendants:表示ViewGroup只有当其子类控件不需要获取焦点时才获取焦点;

blocksDescendants:表示ViewGroup会覆盖子类控件而直接获得焦点。

通常我们用到的是第三种,即在item布局的根布局中添加android:descendantFocusability = “blocksDescendants”,通过此种方式即可解决ListViewitem布局中含有CheckBox时所产生的焦点冲突问题。

焦点冲突问题解决了,接下来需要实现的是如何处理CheckBox的状态,即默认状态为未选中,其次当选中的时候不管是下拉刷新增加新的item还是上拉加载出更多的item都需要保持原来选中的状态,另外新增加的item和加载的item都需要像其它item一样可以对CheckBox进行操作,最后,由于在使用ListView时为了减少对内存的消耗,因此在自定义适配器的时候为了优化ListView都会复用View,这样的话就会造成前面选中CheckBox时后面复用的itemCheckBox也会被选中的问题。

为了满足这一系列的要求,首先需要定义一个用来保存选中位置和对应状态的Map集合并且在ListViewAdapter的构造函数中对其进行初始化,代码如下所示:

/**
* 用来保存选中状态和对应的位置,用于解决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的选择、全选、反选、删除的实现则需要先在ListViewonItemClick事件中对CheckBox的状态进行一些判断和值的处理,如:在编辑状态下,点击item的时候需要对CheckBox的状态进行切换(即CheckBox为选中时需要切换到未选中状态,反之亦然),其次是需要将点击的item的位置及对应的CheckBox的状态值保存到Adapter适配器中定义好的Map<IntegerBoolean>集合中,最后需要对在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> isSelectedList<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

到此,ListViewCheckBox的焦点冲突及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错位等一系列问题的全部内容,希望文章能够帮你解决完美解决ListView和CheckBox焦点冲突及复用时CheckBox错位等一系列问题所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(54)

评论列表共有 0 条评论

立即
投稿
返回
顶部