原文链接:点击打开链接

这篇文章我们将详细的介绍如何实现ListView的多选操作,文中将会纠正在使用ListViewCHOICE_MODE_MULTIPLE或者CHOICE_MODE_MULTIPLE_MODAL时容易犯的错误,以及

CHOICE_MODE_MULTIPLE与CHOICE_MODE_MULTIPLE_MODAL的区别。最后我们将给出一个demo来演示两种多选操作的实现。

一、在不使用ListView多选模式的情况下

注:我认为这一节可以不看,因为我觉得不使用ListView的多选模式有点愚蠢。

如果我们不知道ListView自带多选模式,那么我们一般是通过维护一个保存被选择position集合来实现多选的,通常情况下这个集合类型我们选择HashSet。

实现的大致框架如下:

Adapter中:

保存被选择的position

public HashSet<Long> selectedItems = new HashSet<Long>();

getView中判断当前Position是否在集合中,从而显示不同的外观

public View getView(int position, View convertView, ViewGroup par) {
......if(selectedItems.contains((long)position)){holder.cBox.setChecked(true);}else{holder.cBox.setChecked(false);}if(selectedMode==AppContext.MULTI_SELECTED){holder.cBox.setVisibility(View.VISIBLE);holder.check_box_wraper.setVisibility(View.VISIBLE);}else{holder.cBox.setVisibility(View.GONE);holder.check_box_wraper.setVisibility(View.GONE);}
.....
}

Activity中:

主要是处理onItemClick事件,在不同模式下,做不同的处理。

@Override
public void onItemClick(AdapterView<?> a, View v, int position, long id) {//普通模式 :直接打开一个activityif(itemClickActionMode==AppContext.VIEW_NOTE){Long mId=Long.parseLong(idText.getText().toString());Uri uri = ContentUris.withAppendedId(getIntent().getData(), mId);startActivity(new Intent(Intent.ACTION_VIEW, uri));}//多选模式:更新adapter中selectedItems 集合的值,同时 让adapter在getView中改变item的外观。else{ViewHolder vHollder = (ViewHolder) v.getTag();if(mAdapter.selectedItems.contains((long)position)){mAdapter.selectedItems.remove((long)position);}else{mAdapter.selectedItems.add((long)position);}mAdapter.notifyDataSetChanged();onItemSelected(getSelectedCount());     }}

上面的做法其实用的很普遍。但是我们不提倡。

二、使用ListView的CHOICE_MODE_MULTIPLE模式

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_NONE是普通模式,CHOICE_MODE_SINGLE是单选模式,不常用,CHOICE_MODE_MULTIPLECHOICE_MODE_MULTIPLE_MODAL都是多选模式,他们的区别稍后我们会讲到。

所以ListView在设计的时候其实是考虑了多选操作的,我们没有必要自己再像第一节描述的那样专门维护一个HashSet来保存被选择的position。实现ListView的多选操作的代码在ListView直接父类AbsListView中,AbsListView已经有一个mCheckStates变量来做了保存被选择的position这个事情。mCheckStates的定义如下:

SparseBooleanArray mCheckStates;

AbsListView还定义了如下公共方法:

//判断一个item是否被选中

public boolean isItemChecked(int position);

// 获得被选中 item 的总数

public int getCheckedItemCount();

// 选中一个 item

public void setItemChecked(int position, boolean value);

/ / 清除选中的 item

public void clearChoices();

当点击一个item的时候absListView中会调用performItemClick,如果是CHOICE_MODE_MULTIPLE,则该item点击一次,mCheckStates中相应位置的状态变更一次。然后我们就可以通过listView的getCheckedItemCount()方法获取选择了多少个;isItemChecked(int position)方法判断一个item是不是被选中。

有了这些原生sdk的支持,难道还有什么多选操作是不能实现的吗?所以是不是应该考虑放弃第一节中描述的那种方法了呢?遗憾的是很多android开发者即使是用了CHOICE_MODE_MULTIPLE,仍然没有去利用这些ListView自带的功能,估计是根本不知道该CHOICE_MODE_MULTIPLE的 特性吧,这其实也是android程序员与ios程序员真正存在差距的地方。

CHOICE_MODE_MULTIPLE实战

先看看效果图

package com.example.listmultichoise;
import android.os.Bundle;
import android.app.ActionBar;
import android.app.Activity;
import android.util.Log;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
public class ChoiceModeMultipleActivity extends Activity {ListView mListView = null;MyListAdapter mAdapter;private View mMultiSelectActionBarView;private TextView mSelectedCount;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);getWindow().requestFeature(Window.FEATURE_ACTION_BAR);setContentView(R.layout.activity_list);mListView = (ListView)findViewById(R.id.list);mAdapter = new MyListAdapter(this,mListView);mListView.setAdapter(mAdapter);mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);mListView.setOnItemClickListener(new OnItemClickListener() {public void onItemClick(AdapterView<?> parent, View view, int position,long id) {mAdapter.notifyDataSetChanged();updateSeletedCount();}});if (mMultiSelectActionBarView == null) {mMultiSelectActionBarView = LayoutInflater.from(ChoiceModeMultipleActivity.this).inflate(R.layout.list_multi_select_actionbar, null);mSelectedCount =(TextView)mMultiSelectActionBarView.findViewById(R.id.selected_conv_count);}getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_SHOW_HOME |ActionBar.DISPLAY_SHOW_TITLE);getActionBar().setCustomView(mMultiSelectActionBarView);((TextView)mMultiSelectActionBarView.findViewById(R.id.title)).setText(R.string.select_item);}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {getMenuInflater().inflate(R.menu.multi_select_menu, menu);return true;}@Overridepublic boolean onPrepareOptionsMenu(Menu menu) {MenuItem mItem = menu.findItem(R.id.action_slelect);if(mListView.getCheckedItemCount() == mAdapter.getCount()){mItem.setTitle(R.string.action_deselect_all);}else{mItem.setTitle(R.string.action_select_all);}return super.onPrepareOptionsMenu(menu);}@Overridepublic boolean onOptionsItemSelected(MenuItem item) {switch (item.getItemId()) {case R.id.action_slelect:if(mListView.getCheckedItemCount() == mAdapter.getCount()){unSelectedAll();}else{selectedAll();}mAdapter.notifyDataSetChanged();break;default:break;}return super.onOptionsItemSelected(item);}public void selectedAll(){for(int i= 0; i< mAdapter.getCount(); i++){mListView.setItemChecked(i, true);}updateSeletedCount();}public void unSelectedAll(){mListView.clearChoices();updateSeletedCount();}public void updateSeletedCount(){mSelectedCount.setText(Integer.toString(mListView.getCheckedItemCount()));}
}

代码解释:

首先设置ListView模式:

mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);

定义一个adapter,当ListView的某个item被选中之后,将该Item的背景设置为蓝色,以标记为选中。不然虽然ListView知道该item被选中,但是界面上没表现出来。

......public View getView(int position, View convertView, ViewGroup parent) {TextView tv;if (convertView == null) {tv = (TextView) LayoutInflater.from(mContext).inflate(android.R.layout.simple_expandable_list_item_1, parent,false);} else {tv = (TextView) convertView;}tv.setText(mStrings[position]);updateBackground(position , tv);return tv;}@SuppressLint("NewApi")public void updateBackground(int position, View view) {int backgroundId;if (mListView.isItemChecked(position)) {backgroundId = R.drawable.list_selected_holo_light;} else {backgroundId = R.drawable.conversation_item_background_read;}Drawable background = mContext.getResources().getDrawable(backgroundId);view.setBackground(background);}
......

在item每被点击一次中通知adapter,这样做的目的是为了让更新Ui以显示最新的选中状态。

mListView.setOnItemClickListener(new OnItemClickListener() {public void onItemClick(AdapterView<?> parent, View view, int position,long id) {mAdapter.notifyDataSetChanged();updateSeletedCount();}
});

其中mSelectedCount()作用是在actionbar中更新选中的数目

public void updateSeletedCount(){mSelectedCount.setText(Integer.toString(mListView.getCheckedItemCount()));}

上面的代码实现了多选操作,但是在我选中一个item的时候,listView的onItemClick也同时触发,而一个ListView点击item的后续操作一般是切换到另外一个界面,所以实际应用中,我们还需要设置一个标志位,用来区别当前是多选状态还是普通状态 ,如果是多选状态,调用ListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);  如果是普通状态调用mListView.setChoiceMode(ListView.CHOICE_MODE_NONE);  CHOICE_MODE_MULTIPLE模式的特点在于他本身没有排斥性,在能选择item的情况下,也可以响应普通点击事件 。为了解决这个问题 ,在android3.0之后增加了CHOICE_MODE_MULTIPLE_MODAL模式。

二、使用ListView的CHOICE_MODE_MULTIPLE_MODAL模式

CHOICE_MODE_MULTIPLE_MODAL和CHOICE_MODE_MULTIPLE恰恰相反,他是对普通点击操作和多选操作是排斥的,一旦有一个item被选中,即进入到多选状态,item的onclick事件被屏蔽。这种排斥性也是他比CHOICE_MODE_MULTIPLE多了个MODAL的原因。此外CHOICE_MODE_MULTIPLE_MODAL还结合了android3.0的actionmode,当进入多选状态,actionbar的位置会显示新的菜单。

我们来看看CHOICE_MODE_MULTIPLE_MODAL模式的实现原理:

如何实现两种状态的互斥:当点击一个item的时候absListView中会调用performItemClick,如果是CHOICE_MODE_MULTIPLE,则该item点击一次,mCheckStates中相应位置的状态变更一次,但是CHOICE_MODE_MULTIPLE_MODAL模式不同,必须要mChoiceActionMode!= null

的情况下,才会去变更mCheckStates中相应位置的状态;不光如此,如果mChoiceActionMode!= null

,他还会阻挡ItemClick事件的继续传播,从而屏蔽了ListView OnItemClickListener的onItemClick方法。

如何启用actionmode:一般我们使用actionmode都是在activity中调用startActionMode,但是如果你要使用ListView的CHOICE_MODE_MULTIPLE_MODAL,请不要这么做, 在absListView中有一个变量mChoiceActionMode,定义如下:

ActionMode mChoiceActionMode;

当长按item 或者是调用主动调用setItemChecked方法mChoiceActionMode将被实例化,而如果你是在activity中调用startActionMode,那么虽然actionbar上的菜单变化了,ListView 中的mChoiceActionMode却没有实例化,刚刚我们谈到mChoiceActionMode==null 表示未进入到多选状态,所以这时你点击一个item其实还是普通的点击行为。

因此在CHOICE_MODE_MULTIPLE_MODAL模式下要启用多选操作,只有两种办法:

(1)长按当长按item ;

主动调用ListView的setItemChecked(int position, boolean value)方法选中一个item。

但是这两种进入多选状态的方法都有一个弊端,那就是进入多选状态之后,总是有一个item是被选中的, 方法(1)中长按item,被按的item被选中,这种结果是合理的可以接受的,但是如果你想主动进入多选状态(比如我在点击actionbar的某个菜单的时候想进入多选状态),就必须采用方法(2):调用setItemChecked,这就出现个问题,你该让哪个item被选中呢?貌似最合理的该是一个都不选中吧,我只是进入到这个状态,还没有开始选呢。幸运的是,我们可以使用一些技巧,实现能主动进入多选状态,且没有一个item被选中。

思路是我们先让第一个item被选中,这样Listview就进入多选状态,然后我们再清除被选中item的状态,代码如下:

if(item.getItemId() == R.id.action_choice){mListView.setItemChecked(0,true);mListView.clearChoices();
}

有些人可能会问,按照上面的思路,为什么不这样实现呢:

if(item.getItemId() == R.id.action_choice){mListView.setItemChecked(0,true);mListView.setItemChecked(0,false);
}

嘿嘿,刚刚我们提到ListView CHOICE_MODE_MULTIPLE_MODAL模式中, 一旦有一个item被选中,即进入到多选状态 ,而他还有个相反的特性,一旦所有的Item被主动的设置为未选中,则退出多选状态,mChoiceActionMode会调用自己的finish()方法。为什么呢?在MultiChoiceModeWrapper类中:

@Override
public void onItemCheckedStateChanged(ActionMode mode,int position, long id, boolean checked) {mWrapped.onItemCheckedStateChanged(mode, position, id, checked);// If there are no items selected we no longer need the selection mode.if (getCheckedItemCount() == 0) {mode.finish();}
}

好了我们来实现一个CHOICE_MODE_MULTIPLE_MODAL模式下的多选操作:

代码:

package com.example.listmultichoise;
import android.os.Bundle;
import android.app.Activity;
import android.util.Log;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
public class ChoiceModeMultipleModalActivity extends Activity {ListView mListView = null;MyListAdapter mAdapter;ModeCallback mCallback;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_list);mListView = (ListView)findViewById(R.id.list);mAdapter = new MyListAdapter(this,mListView);mListView.setAdapter(mAdapter);mCallback = new ModeCallback();mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);mListView.setMultiChoiceModeListener(mCallback);mListView.setOnItemClickListener(new OnItemClickListener() {public void onItemClick(AdapterView<?> parent, View view, int position,long id) {Toast.makeText(ChoiceModeMultipleModalActivity.this, "选择了一个item", 300).show();}});}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {// Inflate the menu; this adds items to the action bar if it is present.getMenuInflater().inflate(R.menu.main, menu);return true;}@Overridepublic boolean onOptionsItemSelected(MenuItem item) {if(item.getItemId() == R.id.action_choice){//这里使用了一点技巧来实现处于选中状态 但是0个item 被选择mListView.setItemChecked(0,true);mListView.clearChoices();mCallback.updateSeletedCount();}return super.onOptionsItemSelected(item);}private class ModeCallback implements ListView.MultiChoiceModeListener {private View mMultiSelectActionBarView;private TextView mSelectedCount;@Overridepublic boolean onCreateActionMode(ActionMode mode, Menu menu) {// actionmode的菜单处理MenuInflater inflater = getMenuInflater();inflater.inflate(R.menu.multi_select_menu, menu);if (mMultiSelectActionBarView == null) {mMultiSelectActionBarView = LayoutInflater.from(ChoiceModeMultipleModalActivity.this).inflate(R.layout.list_multi_select_actionbar, null);mSelectedCount =(TextView)mMultiSelectActionBarView.findViewById(R.id.selected_conv_count);}mode.setCustomView(mMultiSelectActionBarView);((TextView)mMultiSelectActionBarView.findViewById(R.id.title)).setText(R.string.select_item);return true;}@Overridepublic boolean onPrepareActionMode(ActionMode mode, Menu menu) {if (mMultiSelectActionBarView == null) {ViewGroup v = (ViewGroup)LayoutInflater.from(ChoiceModeMultipleModalActivity.this).inflate(R.layout.list_multi_select_actionbar, null);mode.setCustomView(v);mSelectedCount = (TextView)v.findViewById(R.id.selected_conv_count);}         //更新菜单的状态MenuItem mItem = menu.findItem(R.id.action_slelect);if(mListView.getCheckedItemCount() == mAdapter.getCount()){mItem.setTitle(R.string.action_deselect_all);}else{mItem.setTitle(R.string.action_select_all);}return true;}@Overridepublic boolean onActionItemClicked(ActionMode mode, MenuItem item) {switch (item.getItemId()) {case R.id.action_slelect:if(mListView.getCheckedItemCount() == mAdapter.getCount()){unSelectedAll();}else{selectedAll();}mAdapter.notifyDataSetChanged();break;default:break;}return true;}@Overridepublic void onDestroyActionMode(ActionMode mode) {mListView.clearChoices();}@Overridepublic void onItemCheckedStateChanged(ActionMode mode,int position, long id, boolean checked) {updateSeletedCount();mode.invalidate();mAdapter.notifyDataSetChanged();}public void updateSeletedCount(){mSelectedCount.setText(Integer.toString(mListView.getCheckedItemCount()));}}public void selectedAll(){for(int i= 0; i< mAdapter.getCount(); i++){mListView.setItemChecked(i, true);}mCallback.updateSeletedCount();}public void unSelectedAll(){mListView.clearChoices();mListView.setItemChecked(0,false);mCallback.updateSeletedCount();}
}

这里需要提醒的是虽然ListView的mActionMode我们不能直接操作,但是actionmode的回调方法是可以在activity中设置的:

mListView.setMultiChoiceModeListener(mCallback);

而且这个回调方法比一般的actionmode回调方法多了个onItemCheckedStateChanged

@Override
public void onItemCheckedStateChanged(ActionMode mode,int position, long id, boolean checked) {....
}

demo我已经上传到了csdn: http://download.csdn.net/detail/jianghejie123/8126071

ListView多选操作模式详解相关推荐

  1. python文件操作模式是什么,python --文件操作模式详解

    #f= open(r"aaa/a.txt",mode='rt') # f的值是一种变量,占用的是应用程序的内存空间,此时牵扯的是两个方面的资源 #print(f) ##2.操作文件 ...

  2. Android心得4.1--文件的保存与读取及文件的操作模式详解.doc

    一.保存到手机内存 1.  很多时候我们的软件需要对处理后的数据进行存储或再次访问.Android为数据存储提供了多种方式,分别有如下几种: l     文件(采用IO数据流的方式) l     Sh ...

  3. 【Tools】VMware虚拟机三种网络模式详解和操作

    00. 目录 文章目录 00. 目录 01. VMware虚拟机三种网络模式 02. Bridged(桥接模式) 03. NAT(地址转换模式) 04. Host-Only(仅主机模式) 05. 参考 ...

  4. 模板方法模式详解附有代码案例分析(包含模板方法模式重构JDBC操作业务代码示例)

    模板方法模式 一.模板方法模式的概念和角色 (一).模板方法模式的概念 (二).模板方法模式的角色 二.模板方法模式的应用场景 三. 模板方法模式的代码示例 四.模板方法模式重构JDBC操作业务 五. ...

  5. eslint php,ESlint操作步骤详解

    这次给大家带来ESlint操作步骤详解,ESlint操作的注意事项有哪些,下面就是实战案例,一起来看一下. vue-cli脚手架创建的项目默认使用ESlint规则,启动项目的时候因为各种语法报错,不得 ...

  6. (十八)享元模式详解(都市异能版) - 转

    作者:zuoxiaolong8810(左潇龙),转载请注明出处. 魔都. 自从越狱风波过去以后,小左的生活便又回到了之前的节奏,依旧是每日徘徊在魔都某天桥,继续着自己的算命之旅. 说起这次越狱风波,着 ...

  7. Docker(十四):Docker:网络模式详解

    Docker作为目前最火的轻量级容器技术,牛逼的功能,如Docker的镜像管理,不足的地方网络方面. Docker自身的4种网络工作方式,和一些自定义网络模式 安装Docker时,它会自动创建三个网络 ...

  8. linux apache两种工作模式详解

    apache两种工作模式详解 刚接触这两个配置时很迷糊,全部开启或全部注释没有几多变化.今天搜索到这么一篇讲得还不错的文章,看了几篇,还是不能完全记住,做一个收藏. 空闲子进程:是指没有正在处理请求的 ...

  9. cglib动态代理jar包_代理模式详解:静态代理+JDK/CGLIB 动态代理实战

    1. 代理模式 代理模式是一种比较好的理解的设计模式.简单来说就是 我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标 ...

最新文章

  1. iOS开展——全球应对MotionEvent
  2. 史密斯圆图串并联口诀_看得懂的史密斯圆图(个人总结)
  3. ceph架构/IO原理分析(齐全)
  4. HttpServletRequest 对象
  5. linux mysql数据库定时备份
  6. 服务器经常崩溃??让我们来看看简单的内存知识:C语言——内存管理
  7. Binary String Matching
  8. 经典网络分析 - Very Deep Convolutional Networks for Large-Scale Image Recognition(VGG)
  9. 字符串拼接与打印相关2
  10. 10个Python实战编程项目,有趣又好玩
  11. OMNeT 例程 Tictoc15 和 Tictoc16 矢量和标量统计详解
  12. STM32 PWM占空比和信号周期的控制因素分析
  13. v全球与中国男士西装市场经营规模分析与投资前景研究报告2022年版
  14. 剖析拒绝服务攻击-SYN拒绝服务(转)
  15. 电动汽车的N种充电方式
  16. Python使用 matplotlib的basemap绘图之一--几行代码画世界地图和中国地图
  17. 3D dungeon(BFS)
  18. 怎么让上下两排对齐_word中如何将上下两行间字、字符、数字分别对齐
  19. 6. ESP8266固件的下载
  20. 微信好友删除了怎么找回来?

热门文章

  1. Go版GTK:环境搭建(windows)
  2. 为啥app没有menu键?
  3. 有一个超毒舌的对象是什么体验?
  4. TwinCAT更改背景主题颜色
  5. [css]版心和布局流程
  6. 视频后期合成软件:Nuke 13 for Mac(支持m1) v13.0v3激活版
  7. 移动web开发--移动端常见布局+流式布局和flex布局+携程网首页案例
  8. DD模型--传统金融中介会被互联网金融打败吗?
  9. 弹性盒之主轴与交叉轴的区分
  10. MathNet.Numerics主要类功能简述