本文介绍了我们将如何取得具体ListView多选择操作。本文将正确使用ListViewCHOICE_MODE_MULTIPLE要么CHOICE_MODE_MULTIPLE_MODAL时间easy误区。以及

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_MULTIPLECHOICE_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的更多相关文章

  1. HearthBuddy中_settings.txt的更详细参数解释

    https://tieba.baidu.com/p/5275382967 默认的配置不是很合理,花了点时间读了下silverfish(也就是兄弟用的AI)的代码后也尝试修改了些参数,有没有效果仁者见仁 ...

  2. ListView多选操作模式详解CHOICE_MODE_MULTIPLE与CHOICE_MODE_MULTIPLE_MODAL

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

  3. ListView多选操作模式——上下文操作模式

    1.什么叫上下文操作模式 2.如何进入上下文操作模式 1.ListView自身带了单选.多选模式,可通过listview.setChoiceMode来设置: listview.setChoiceMod ...

  4. day08文件操作的三步骤,基础的读,基础的写,with...open语法,文件的操作模式,文件的操作编码问题,文件的复制,游标操作

    复习 ''' 类型转换 1.数字类型:int() | bool() | float() 2.str与int:int('10') | int('-10') | int('0') | float('-.5 ...

  5. 关于NLB的群集操作模式知识 (转载)

    单播:单播模式是指各节点的网络适配器被重新指定了一个虚拟MAC(由02-bf和群集IP地址组成确保此MAC的唯一性).由于所有绑定群集的网络适配器的MAC都相同,所以在单网卡的情况下,各节点之间是不能 ...

  6. python文件对象几种操作模式区别——文件操作方法详解

    文件对象的字节模式/b模式(以utf-8编码为例) 读操作 写操作 指针操作 ASCII字节 返回bytes/字节类型的Ascii 写入bytes类型字节 例如:b'This is ascii' 使用 ...

  7. juniper防护墙接口的NAT和ROUTE模式如何选择问题

    juniper防护墙一般是把trust接口部署为NAT模式,untrust接口部署为route模式.这样当来自内部的数据访问Utrust区域时,会把源地址翻译成untrust接口的地址.从而达到隐藏内 ...

  8. pytthon—day8 读写模式的结合、文件操作模式、with完成文本文件复制、游标操作

    一.读写模式的结合 w:写指没有新建文件,有文件就清空 w=open('1.txt','w',encoding='utf-8') w.write('000\n') 在写入数据时,需要及时处理内存空间, ...

  9. day8 八、文件操作模式、文件的复制与文件游标操作

    一.文件操作 1.wr模式结合 ① w = open('1.txt', 'w', encoding='utf-8') # w:没有文件新建文件,有文件就清空文件 w.write('000\n') w. ...

随机推荐

  1. hdu3496(二维背包)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3496 题意:题意是 DuoDuo 想看n部电影,但是被要求最长能看的总时间数为 L,每部电影有他的时长 ...

  2. 实习第一天之数据绑定:<%#Eval("PartyName")%>'

    1.asp:HyperLink ID="Link" runat="server" Target="_blank" Text='<%#E ...

  3. HTML解析HtmlAgilityPack

    原文:HTML解析HtmlAgilityPack //解析页面源代码            Uri surl = new Uri(url);            Uri uriCategory = ...

  4. 【转】Acm之java速成

    这里指的java速成,只限于java语法,包括输入输出,运算处理,字符串和高精度的处理,进制之间的转换等,能解决OJ上的一些高精度题目. 1. 输入:格式为:Scanner cin = new Sca ...

  5. Oracle 最简单的随系统自己主动启动

    Oracle 最简单的随系统自己主动启动 俗话说用户是上帝,他们有时候提出一个问题很的简单,就仅仅须要一句话,一分钟就完事了.可是拿到我们DBA来说,可能至少得半个小时甚至半个月才干满足他的一句话.有 ...

  6. hadoop ,传智播客目录

    一.Hadoop入门,了解什么是Hadoop 1.Hadoop产生背景 2.Hadoop在大数据.云计算中的位置和关系 3.国内外Hadoop应用案例介绍 4.国内Hadoop的就业情况分析及课程大纲 ...

  7. 怎样在C++中获得完整的类型名称

    Wrote by mutouyun. (http://darkc.at/cxx-get-the-name-of-the-given-type/) 地球人都知道C++里有一个typeid操作符能够用来获 ...

  8. NET通用平台

    NET通用平台.通用权限.易扩展.多语言.多平台架构框架 先拿出我半前年前平台的设计初稿,经过半年的努力我已经完成了该设计稿的所有功能.并且理念已经远远超出该设计稿. 下面是一些博友对我贴子的评价: ...

  9. java中final的意义

    1.如果一个数据既是static又是final,那么它会拥有一块无法改变的存储空间. 2.final data: 当final用于基本数据类型时,final让其值(value)保持不变,但是当用于ob ...

  10. Java4Android之BlockingQueue

    在研究Smack的源码的时候,我对它的连接Connection以及派生类XMPPConnection的关注是最多的,由于一个即时通信程序,它的网络模块必是它的核心. 而我非常在乎它是怎样实现的. 在收 ...