完美解决ListView和CheckBox焦点冲突及复用时CheckBox错位等一系列问题

来源:互联网 发布:淘宝不能用ie8浏览器 编辑:程序博客网 时间:2024/06/06 01:52

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

源码


0 0
原创粉丝点击