需求驱动技术,最近在项目中又遇到这样一个需求,简单而言就是:遍历某个文件夹下的所有log文件,然后将它们通过微信发送给别人。
这个功能很容易实现,但是在实现过程中,我希望自己的产品使用起来更加的人性化,所有我添加了多文件压缩打包功能,多选,反选,全选等功能,这样使用者就可以更加合理选择自己需要的log了。
那么问题来了,ListView应该怎么实现多选功能呢?
有很多朋友也许不了解上下文操作模式是什么,我们来看Google是怎么说的:
上下文操作模式是 ActionMode 的一种系统实现,它将用户交互的重点转到执行上下文操作上。用户通过选择项目启用此模式时,屏幕顶部将出现一个“上下文操作栏”,显示用户可对当前所选项执行的操作。 启用此模式后,用户可以选择多个项目(若您允许)、取消选择项目以及继续在 Activity 内导航(在您允许的最大范围内)。 当用户取消选择所有项目、按“返回”按钮或选择操作栏左侧的“完成”操作时,该操作模式将会禁用,且上下文操作栏将会消失。
该模式还提供了一些方法
//判断一个item是否被选中
public boolean isItemChecked(int position);
//获得被选中item的总数
public int getCheckedItemCount();
//选中一个item
public void setItemChecked(int position, boolean value);
//清除选中的item
public void clearChoices();
希望了解得更加详细,可以参考菜单
从上面我们知道了这些信息:
值得一提的是,第2项有一些坑,在下面我会提到。
而第3项,造成了一个我还没有解决的bug,我期待按下”完成”按钮以后,将压缩所选log文件,而按下“返回”键,不做任何操作,退出上下文模式。然而我没有找到区分这两个键的办法。
我尝试在上下文模式中,监听”back”键,然而back键的响应,居然在上下文模式消失以后,所以还是没有办法区分。
好了,我自己的迷惑就说到这里,下面我们来看listView怎么进入上下文模式。
ListView有四种模式:
/**
* Normal list that does not indicate choices
* 普通模式
*/
public static final int CHOICE_MODE_NONE = 0;
/**
* The list allows up to one choice
* 单选模式,不常用
*/
public static final int CHOICE_MODE_SINGLE = 1;
/**
* The list allows multiple choices
* 多选模式,无排除性
*/
public static final int CHOICE_MODE_MULTIPLE = 2;
/**
* The list allows multiple choices in a modal selection mode
* 多选模式,有排除性
*/
public static final int CHOICE_MODE_MULTIPLE_MODAL = 3;
说说CHOICE_MODE_MULTIPLE和CHOICE_MODE_MULTIPLE_MODAL的区别
CHOICE_MODE_MULTIPLE:进入该多选模式以后,点击item,item除了被选中,还会响应它的onClick()事件
CHOICE_MODE_MULTIPLE_MODAL:进入该多选模式以后,点击item,item被选中,不响应点击事件
于是根据我自己的需求,我选中了CHOICE_MODE_MULTIPLE_MODAL模式,因为我需要在不进入多选模式前,点击item,就看发送单个log。
在代码中,进入多选模式:
private void initActionMutiply(){
//多选模式
loglist.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
//监听模式状态
if(mCallback==null) mCallback = new ModeCallback();
loglist.setMultiChoiceModeListener(mCallback);
//清除所有选中
loglist.clearChoices();
}
在调用上面的代码以后,我们只要长按item,就会自动进入多选模式。
进入多选模式以后,我希望可以显示当前的选中项,但是由于在这个模式中,item被选中以后,是不会产生样式变化的,所以在adapter里面,我们要检查item的状态,主动修改其样式,例如这里我就讲背景颜色变成蓝色
@Override
public void convert(ViewHolder holder, LogInfo t) {
holder.setText(R.id.name, "log名称:"+t.logName);
holder.setText(R.id.createTime, "创建时间:"+t.getFormatTime());
holder.setText(R.id.size, "文件大小:"+t.getSize());
if (mListView.isItemChecked(holder.getmPosition())) {//判断是否被选中
holder.setBackgroundColor(R.id.log_list_item_layout, Color.BLUE);
} else {
holder.setBackgroundColor(R.id.log_list_item_layout, Color.WHITE);
}
}
另外我希望看到我选中了多少项,所以有必要自定义一下ActionBar的布局,当然我们还希望退出多选模式以后,回复原来的ActionBar,所以这里我们有一个监听器
private class ModeCallback implements ListView.MultiChoiceModeListener {
private View mMultiSelectActionBarView;
private TextView mSelectedCount;
private ArrayList arr = new ArrayList();
@Override
/**
* 进入多选模式,这个方法首先被调用
* 在这里我们新建菜单(多选,反选等)
* 修改ActionBar样式,使其可以显示当前选中的item数目
*/
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
// actionmode的菜单处理
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.multi_select_menu, menu);//新建菜单
if (mMultiSelectActionBarView == null) {
mMultiSelectActionBarView = LayoutInflater.from(LogListActivity.this)
.inflate(R.layout.list_multi_select_actionbar, null);
mSelectedCount =
(TextView)mMultiSelectActionBarView.findViewById(R.id.selected_conv_count);
((TextView)mMultiSelectActionBarView.findViewById(R.id.title)).setText("已选择");
}
mode.setCustomView(mMultiSelectActionBarView);//设置新的ActionBar样式
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
case R.id.action_slelect_all:
selectAll();
return true;
case R.id.action_slelect_opposite:
selectOpposite();
return true;
case R.id.action_slelect_cancel:
loglist.clearChoices();
mode.finish();
return true;
case R.id.action_slelect_share:
shareChoices();
return true;
case R.id.action_slelect_delete:
selectDelete();
return true;
default:
return true;
}
}
//分享
private synchronized void shareChoices(){
arr.clear();
for(int i= 0; i< mLogListAdapter.getCount(); i++){
if(loglist.isItemChecked(i)){
arr.add(LogShareHelper.logArray.get(i));
}
}
if(arr.size()!=0) shareAllLogs(arr);
}
/**
* 退出多选模式的时候,这个方法会被调用
*/
@Override
public void onDestroyActionMode(ActionMode mode) {
isFirst = false;
loglist.clearChoices();
}
/**
* 当item选中状态改变时调用
* 在这里修改通知adapter修改item背景色,更新选中数目
*/
@Override
public void onItemCheckedStateChanged(ActionMode mode,
int position, long id, boolean checked) {
if(isFirst){
mSelectedCount.setText("0");
isFirst = false;
}else{
mSelectedCount.setText(loglist.getCheckedItemCount()+"");
}
mLogListAdapter.notifyDataSetChanged();
}
//全选功能
private void selectAll(){
for(int i= 0; i< mLogListAdapter.getCount(); i++){
loglist.setItemChecked(i, true);
}
}
//反选
private void selectOpposite(){
...
}
//删除选中
private void selectDelete(){
...
}
}
}
在上面的代码中看到,ListView.MultiChoiceModeListener可以监听多选模式的状态,所以我根据这些状态,做相应的操作。
在前文中我们注意到,进入多选模式,可以长按item
但是如果我们希望通过点击一个按钮,进入多选模式怎么办呢?
要进入多选模式,就必须有一个item被选中,但是如果我们调用
mListView.setItemChecked(0,true);
来使第一项被选中,从而进入多选模式。这样会造成用户体验问题,也许用户并不想选中第一项。
那么,我之后调用
mListView.setItemChecked(0,false);
来清除第一项的选中,是不是就可以了呢?
现在请参考上面提到的上下文菜单的第2项特性,一旦没有选中项,就会退出多选模式。
所以解决办法是,接下来再调用一句
mListView.clearChoices();
就可以清除选中状态了,而且不会退出多选模式(并且不会调用监听器的onItemCheckedStateChanged(ActionMode mode,int position, long id, boolean checked)方法)。
参考源码下载