ListView多选操作模式详解CHOICE_MODE_MULTIPLE与CHOICE_MODE_MULTIPLE_MODAL
这篇文章我们将详细的介绍如何实现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) {
- //普通模式 :直接打开一个activity
- if(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());
- }
- }
上面的做法其实用的很普遍。但是我们不提倡。
二、使用ListViiew的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_MULTIPLE
和CHOICE_MODE_MULTIPLE_MODAL
都是多选模式,他们的区别稍后我们会讲到。
所以ListView在设计的时候其实是考虑了多选操作的,我们没有必要自己再像第一节描述的那样专门维护一个HashSet来保存被选择的position。实现ListView的多选操作的代码在ListView直接父类AbsListView中,AbsListView已经有一个mCheckStates变量来做了保存被选择的position这个事情。mCheckStates的定义如下:
1
|
SparseBooleanArray mCheckStates; |
AbsListView还定义了如下公共方法:
//判断一个item是否被选中
1
|
public boolean isItemChecked(int position); |
//获得被选中item的总数
1
|
public int getCheckedItemCount(); |
//选中一个item
1
|
public void setItemChecked(int position, boolean value); |
//清除选中的item
1
|
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;
- @Override
- protected 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);
- }
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- getMenuInflater().inflate(R.menu.multi_select_menu, menu);
- return true;
- }
- @Override
- public 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);
- }
- @Override
- public 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模式。
二、使用ListViiew的CHOICE_MODE_MULTIPLE模式
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 ;
(2)主动调用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;
- @Override
- protected 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();
- }
- });
- }
- @Override
- public 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;
- }
- @Override
- public 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;
- @Override
- 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(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;
- }
- @Override
- public 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;
- }
- @Override
- public 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;
- }
- @Override
- public void onDestroyActionMode(ActionMode mode) {
- mListView.clearChoices();
- }
- @Override
- public 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多选操作模式详解CHOICE_MODE_MULTIPLE与CHOICE_MODE_MULTIPLE_MODAL的更多相关文章
- ListView多选操作模式——上下文操作模式
1.什么叫上下文操作模式 2.如何进入上下文操作模式 1.ListView自身带了单选.多选模式,可通过listview.setChoiceMode来设置: listview.setChoiceMod ...
- SQLServer 常见SQL笔试题之语句操作题详解
SqlServer 常见SQL笔试题之语句操作题详解 by:授客 QQ:1033553122 测试数据库 CREATE DATABASE handWriting ON PRIMARY ( name = ...
- Extjs MVC开发模式详解
Extjs MVC开发模式详解 在JS的开发过程中,大规模的JS脚本难以组织和维护,这一直是困扰前端开发人员的头等问题.Extjs为了解决这种问题,在Extjs 4.x版本中引入了MVC开发模式, ...
- Javascript设计模式之装饰者模式详解篇
一.前言: 装饰者模式(Decorator Pattern):在不改变原类和继承的情况下动态扩展对象功能,通过包装一个对象来实现一个新的具有原对象相同接口的新的对象. 装饰者模式的特点: 1. 在不改 ...
- SQL Server 表的管理_关于数据增删查改的操作的详解(案例代码)
SQL Server 表的管理_关于数据增删查改的操作的详解(案例代码)-DML 1.SQL INSERT INTO 语句(在表中插入) INSERT INTO 语句用于向表中插入新记录. SQL I ...
- ext.js的mvc开发模式详解
ext.js的mvc开发模式详解和环境配置 在JS的开发过程中,大规模的JS脚本难以组织和维护,这一直是困扰前端开发人员的头等问题.Extjs为了解决这种问题,在Extjs 4.x版本中引入了MVC开 ...
- 安装MACOS操作步骤详解
安装MACOS操作步骤详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 关于安装MAC的操作系统其实大家都知道可以让客服帮忙提供软件上的支持,而且苹果客服都很有礼貌呢,而且非常的 ...
- DES加密模式详解
DES加密模式详解 http://www.cnblogs.com/Lawson/archive/2012/05/20/2510781.html http://www.blogjava.net/wayn ...
- SQL Server 表的管理_关于事务操作的详解(案例代码)
SQL Server 表的管理_关于事务操作的详解(案例代码) 1.概念 事务(transaction): 是将多个修改语句组合在一起的方法,这个方法中的所有语句只有全部执行才能正确完成功能.即要么全 ...
随机推荐
- zepto源码学习-03 $()
在第一篇的时候提到过关于$()的用法,一个接口有很多重载,用法有很多种,总结了下,大概有一以下几种 1.$(selector,context?) 传入一个选择器返回一个zepto对象 2.$(func ...
- Ember.js demo7
<!DOCTYPE html> <html> <head> <script src="http://code.jquery.com/jquery-1 ...
- C 语言函数指针
c代码: #include <stdio.h> int add(int x,int y); int subtract(int x,int y); int domath(int (*math ...
- Java 输入流读取文本文件换行符问题
一问题 在学习流编程的过程中,我遇到了一下问题.首先来看一下我写的java源程序: package StreamLearn; import java.io.*; public class TestFi ...
- mysql 有索引没走索引 更新锁全表
Session 1: mysql> select connection_id(); +-----------------+ | connection_id() | +-------------- ...
- 请问什么是UTF字符串?
utf是编码方式,一般而言是国际性质的编码格式,有utf-8,utf-9,utf-16等多种形式,是最高级别的编码方式,也就是说如果你要读取的数据流设置成utf编码的话就要用到相应的编码方式来读取了, ...
- poj2187
求最远点对,这是一道经典的旋转卡壳的题目话说由于是前年写的,之后就没怎么研究过计算几何了……感觉都不大记得清了,来稍微回忆一下……首先最远点对一定出现在凸包上显然,然后穷举肯定不行,这时候就需要旋转卡 ...
- GitHub常用 库
来自: http://www.jianshu.com/p/6475c90e8b4d 网络请求库 https://github.com/AFNetworking/AFNetworking https:/ ...
- sharepoint 2010 隐藏左边菜单left menu样式脚本
转:http://www.cfanz.cn/?c=article&a=read&id=60536 在v4.master中,<head></head>标签中,加入 ...
- 查询显示MSSQL表结构 [转]
SELECT 表名 = Case When A.colorder= Then D.name Else '' End, 表说明 = Case When A.colorder= Then isnull(F ...