GridView多选错误范例解析

来源:互联网 发布:阿里云系统 编辑:程序博客网 时间:2024/06/04 19:26
最近在写一个程序,需要使用GridView显示很多图片的缩略图。想要实现的效果是长按
进入多选状态,在多选状态点击各个图片能够勾选,并得到所有选择的图片。
最初参考的是这篇文章
http://blog.csdn.net/zhouyuanjing/article/details/8372686
文章里作者提供了源码,为分析方便,贴在下面。


import java.util.HashMap;import java.util.Map;import java.util.Set;import android.app.Activity;import android.content.Context;import android.os.Bundle;import android.view.ActionMode;import android.view.LayoutInflater;import android.view.Menu;import android.view.MenuItem;import android.view.View;import android.view.ViewGroup;import android.widget.AbsListView.LayoutParams;import android.widget.AbsListView.MultiChoiceModeListener;import android.widget.BaseAdapter;import android.widget.Checkable;import android.widget.FrameLayout;import android.widget.GridView;import android.widget.ImageView;import android.widget.ListAdapter;import android.widget.TextView;public class HomeActivity extends Activity implements MultiChoiceModeListener {    private GridView mGridView;    private GridAdapter mGridAdapter;    private TextView mActionText;    private static final int MENU_SELECT_ALL = 0;    private static final int MENU_UNSELECT_ALL = MENU_SELECT_ALL + 1;    private Map<Integer, Boolean> mSelectMap = new HashMap<Integer, Boolean>();    private int[] mImgIds = new int[] { R.drawable.img_1, R.drawable.img_2,            R.drawable.img_3};    /** Called when the activity is first created. */    @Override    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.main);        mGridView = (GridView) findViewById(R.id.gridview);        mGridView.setChoiceMode(GridView.CHOICE_MODE_MULTIPLE_MODAL);        mGridAdapter = new GridAdapter(this);        mGridView.setAdapter(mGridAdapter);        mGridView.setMultiChoiceModeListener(this);    }    /** Override MultiChoiceModeListener start **/    @Override    public boolean onCreateActionMode(ActionMode mode, Menu menu) {        // TODO Auto-generated method stub        View v = LayoutInflater.from(this).inflate(R.layout.actionbar_layout,                null);        mActionText = (TextView) v.findViewById(R.id.action_text);        mActionText.setText(formatString(mGridView.getCheckedItemCount()));        mode.setCustomView(v);        getMenuInflater().inflate(R.menu.action_menu, menu);        return true;    }    @Override    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {        // TODO Auto-generated method stub        menu.getItem(MENU_SELECT_ALL).setEnabled(                mGridView.getCheckedItemCount() != mGridView.getCount());        return true;    }    @Override    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {        // TODO Auto-generated method stub        switch (item.getItemId()) {        case R.id.menu_select:            for (int i = 0; i < mGridView.getCount(); i++) {                mGridView.setItemChecked(i, true);                mSelectMap.put(i, true);            }            break;        case R.id.menu_unselect:            for (int i = 0; i < mGridView.getCount(); i++) {                mGridView.setItemChecked(i, false);                mSelectMap.clear();            }            break;        }        return true;    }    @Override    public void onDestroyActionMode(ActionMode mode) {        // TODO Auto-generated method stub        mGridAdapter.notifyDataSetChanged();    }    @Override    public void onItemCheckedStateChanged(ActionMode mode, int position,            long id, boolean checked) {        // TODO Auto-generated method stub        mActionText.setText(formatString(mGridView.getCheckedItemCount()));        mSelectMap.put(position, checked);        mode.invalidate();    }    /** Override MultiChoiceModeListener end **/    private String formatString(int count) {        return String.format(getString(R.string.selection), count);    }    private class GridAdapter extends BaseAdapter {        private Context mContext;        public GridAdapter(Context ctx) {            mContext = ctx;        }        @Override        public int getCount() {            // TODO Auto-generated method stub            return mImgIds.length;        }        @Override        public Integer getItem(int position) {            // TODO Auto-generated method stub            return Integer.valueOf(mImgIds[position]);        }        @Override        public long getItemId(int position) {            // TODO Auto-generated method stub            return position;        }        @Override        public View getView(int position, View convertView, ViewGroup parent) {            // TODO Auto-generated method stubLog.d(TAG, "getView: "+position+","+(mSelectMap.get(position) == null ? false                    : mSelectMap.get(position)));//<------(2)            GridItem item;            if (convertView == null) {                item = new GridItem(mContext);                item.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,                        LayoutParams.FILL_PARENT));            } else {                item = (GridItem) convertView;            }            item.setImgResId(getItem(position));            item.setChecked(mSelectMap.get(position) == null ? false                    : mSelectMap.get(position));//<-----(1)            return item;        }    }}

其中GridItem是列表项的View类,实现了Checkable接口

import android.content.Context;import android.util.AttributeSet;import android.util.Log;import android.view.LayoutInflater;import android.view.View;import android.widget.Checkable;import android.widget.ImageView;import android.widget.RelativeLayout;public class GridItem extends RelativeLayout implements Checkable {    private Context mContext;    private boolean mChecked;    private ImageView mImgView = null;    private ImageView mSecletView = null;    public GridItem(Context context) {        this(context, null, 0);    }    public GridItem(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public GridItem(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);        // TODO Auto-generated constructor stub        mContext = context;        LayoutInflater.from(mContext).inflate(R.layout.grid_item, this);        mImgView = (ImageView) findViewById(R.id.img_view);        mSecletView = (ImageView) findViewById(R.id.select);    }    @Override    public void setChecked(boolean checked) {        // TODO Auto-generated method stubLog.d(TAG,"setChecked: "+checked);//<---        mChecked = checked;        setBackgroundDrawable(checked ? getResources().getDrawable(                R.drawable.background) : null);        mSecletView.setVisibility(checked ? View.VISIBLE : View.GONE);    }    @Override    public boolean isChecked() {        // TODO Auto-generated method stub        return mChecked;    }    @Override    public void toggle() {        // TODO Auto-generated method stub        setChecked(!mChecked);    }    public void setImgResId(int resId) {        if (mImgView != null) {            mImgView.setBackgroundResource(resId);        }    }}



作者的基本思路就是自己维护一个HashMap,用于记录哪些位置的图片被选中了,然后
根据选中状态在Adapter的getView方法中设置图片的外观。如(1)处所示。


但是经过我的试验,发现这种方法不仅费事,而且极易出错,可以说是吃力不讨好。
因为AbsListView本身就有一个变量来记录各项的选中状态:
SparseBooleanArray mCheckStates;
SparseBooleanArray是android.util包中的一个类,API说它
SparseBooleanArrays map integers to booleans。因此它也相当于是一个Map,将int
类型的key对应到boolean值上。实际上就记录了GridView中的各个position的选择状态
true或者false.


如果我们只是没有用GridView自带的机制来处理选中状态,那还好,顶多算费事点。
但是除了费事之外,自己维护选中状态会随之带来很多问题。
虽然我们在getView中调用GridItem的setChecked方法设置好了列表项的状态(这个状态
依据的是自定义的mSelectMap),但getView返回后,GridView自身还会去调用一次
GridItem的setChecked方法再次设置列表项的状态,而这次调用,依据的是mCheckState
中记录的状态。这个状态,很可能跟mSelectMap不一样。


我们可以做试验来验证。
首先在GridItem的setCheced()方法,getView方法中都添加Log代码,如(2)(3)所示
第一次长按第0项,Logcat输出为:
1、08-05 16:27:13.416: D/GridItem(1812): setChecked: false
2、08-05 16:27:13.416: D/GridItem(1812): setChecked: false
3、08-05 16:27:13.416: D/GridItem(1812): setChecked: false
4、08-05 16:27:14.036: D/HomeActivity(1812): getView: 0,true
5、08-05 16:27:14.036: D/GridItem(1812): setChecked: true
6、08-05 16:27:14.056: D/HomeActivity(1812): getView: 0,true
7、08-05 16:27:14.056: D/GridItem(1812): setChecked: true
8、08-05 16:27:14.076: D/GridItem(1812): setChecked: true
9、08-05 16:27:14.086: D/HomeActivity(1812): getView: 1,false
10、08-05 16:27:14.086: D/GridItem(1812): setChecked: false
11、08-05 16:27:14.086: D/GridItem(1812): setChecked: false
12、08-05 16:27:14.096: D/HomeActivity(1812): getView: 2,false
13、08-05 16:27:14.096: D/GridItem(1812): setChecked: false
14、08-05 16:27:14.096: D/GridItem(1812): setChecked: false
其中1~3行是GridView调用的setChecked,全为false,可以认为是建立初始状态
第5行是我们自己在getView中调用的setChecked;
第6,7行与第4、5行一样,好像每次第一项都会处理两次,不知何故;
第8行是GridView在getView(0)之后调用的setChecked;
同理,第11、14行分别是系统在每次getView之后调用的setChecked。

可以看到,迄今为止,GridView调用的setChecked和我们自己调用的setChecked状态

还是一致的。



好,保持第0项为选中状态,我们点击返回退出多选模式,Logcat输出为:
1、08-05 16:34:30.897: D/HomeActivity(1812): getView: 0,true
2、08-05 16:34:30.897: D/GridItem(1812): setChecked: true
3、08-05 16:34:30.907: D/HomeActivity(1812): getView: 0,true
4、08-05 16:34:30.907: D/GridItem(1812): setChecked: true
5、08-05 16:34:30.907: D/GridItem(1812): setChecked: false
6、08-05 16:34:30.917: D/HomeActivity(1812): getView: 1,false
7、08-05 16:34:30.917: D/GridItem(1812): setChecked: false
8、08-05 16:34:30.917: D/GridItem(1812): setChecked: false
9、08-05 16:34:30.917: D/HomeActivity(1812): getView: 2,false
10、08-05 16:34:30.927: D/GridItem(1812): setChecked: false
11、08-05 16:34:30.937: D/GridItem(1812): setChecked: false
对于第一项,第3、4行是重复第1、2行的;
第5行就有意思了,是由GridView调用的setChecked,为false。

对比第4行和第5行,即GridView认为第0项已经取消选择了,而我们依据mSelectMap

认为第0项还在选择状态。

至此已经出现了视图和数据模型不一致的情况。


长按第2项再次进入多选,Logcat输出如下:
1、08-05 16:39:21.666: D/GridItem(1812): setChecked: false
2、08-05 16:39:21.666: D/GridItem(1812): setChecked: false
3、08-05 16:39:21.688: D/GridItem(1812): setChecked: false
4、08-05 16:39:22.306: D/HomeActivity(1812): getView: 0,true
5、08-05 16:39:22.316: D/GridItem(1812): setChecked: true
6、08-05 16:39:22.348: D/HomeActivity(1812): getView: 0,true
7、08-05 16:39:22.348: D/GridItem(1812): setChecked: true
8、08-05 16:39:22.356: D/GridItem(1812): setChecked: false
9、08-05 16:39:22.356: D/HomeActivity(1812): getView: 1,false
10、08-05 16:39:22.366: D/GridItem(1812): setChecked: false
11、08-05 16:39:22.366: D/GridItem(1812): setChecked: false
12、08-05 16:39:22.366: D/HomeActivity(1812): getView: 2,true
13、08-05 16:39:22.409: D/GridItem(1812): setChecked: true
14、08-05 16:39:22.409: D/GridItem(1812): setChecked: true
第1、2、3行为GridView在设置初始状态
第6、7行重复第4、5行;
第8行和第7行仍旧是不一致的,代表mSelectMap和GridView在第0项是否选择上仍旧是
不一样的。在mSeletMap看来,第0项和第2项处于选择状态;而在GridView看来,只有
第2项处于选择状态。
如果保持第2项选中,退出多选模式,则对于第2项的选择状态,两者也会出现不一致的
情况。我们当然可以在退出多选模式的时候做点什么,使mSelectMap和GridView的
mCheckStates保持一致。但既然我们并不能在getView中借由自己维护的数据模型

mSelectMap来控制外观,选中状态的外观仍然取决于GridView.mCheckStates。那为什

要多此一举自己定义一个mSelectMap并费劲地使它和GridView.mCheckStates保持一

致,而不直接使用GridView.mCheckStates呢?



所以,直接使用AbsListView的mCheckedState及相关的公共方法来设置和查询选中状态
才是正途。下面是AbsListView提供的一些与选中有关的方法。
//判断一个item是否被选中1
public boolean isItemChecked(int position);
//获得被选中item的总数
public int getCheckedItemCount();
//选中一个item
public void setItemChecked(int position, boolean value);
//清除选中的item
public void clearChoices();


详细可参考

http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/1105/1906.html


最后附上我修改的代码

http://download.csdn.net/detail/glorydream2015/8975721

(没积分下载别人的东西了,大家给点分意思一下吧)

0 0
原创粉丝点击