这篇文章我们将详细的介绍如何实现ListView的多选操作,文中将会纠正在使用ListViewCHOICE_MODE_MULTIPLE或者CHOICE_MODE_MULTIPLE_MODAL时容易犯的错误,以及
CHOICE_MODE_MULTIPLE与CHOICE_MODE_MULTIPLE_MODAL的区别。最后我们将给出一个demo来演示两种多选操作的实现。
注:我认为这一节可以不看,因为我觉得不使用ListView的多选模式有点愚蠢。
如果我们不知道ListView自带多选模式,那么我们一般是通过维护一个保存被选择position集合来实现多选的,通常情况下这个集合类型我们选择HashSet。
实现的大致框架如下:
Adapter中:
保存被选择的position
public HashSet<Long> selectedItems = new HashSet<Long>();
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()); } }
上面的做法其实用的很普遍。但是我们不提倡。
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);
...... 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); } ......
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模式。
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); }
@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(); } }
代码:
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(); } }
mListView.setMultiChoiceModeListener(mCallback);
@Override public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) { .... }
ListView多选操作模式详解CHOICE_MODE_MULTIPLE与CHOICE_MODE_MULTIPLE_MODAL
原文地址:http://blog.csdn.net/jianghejie123/article/details/40860565