最近在写一个程序,需要使用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 stub
Log.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 stub
Log.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
(没积分下载别人的东西了,大家给点分意思一下吧)