在平时的使用中,我们经常会用到ListView的多选功能,一般比较常见的方式是通过在itemView中加入一个checkBox来实现。其实ListView的API中提供了用于实现多选功能的相关接口。可以通过设置ListView的choiceMode
属性来实现listview
的item项在长按时激活多选的功能,再配合ListView.MultiChoiceModeListener
的回调实现多选。
下面具体来看一下实现方法。
然后在MainActivity中对这个listview进行初始化,下面是onCreate中的代码
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
mDatas = new ArrayList<>();
for (int i = 0; i < 30; i++) {
mDatas.add("Item " + i);
}
mAdapter = new MyAdapter(this, mDatas);
mMultiChoiceListener = new MultiChoiceListener();
mListView = (ListView) findViewById(R.id.lv);
mListView.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE_MODAL);
mListView.setMultiChoiceModeListener(mMultiChoiceListener);
}
其中关键的代码是mListView.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE_MODAL);
mListView.setMultiChoiceModeListener(mMultiChoiceListener);
第一行是设置listview的选择模式,第二行是为listview设置一个多选时的回调事件。这个mMultiChoiceListener
是一个实现了AbsListView.MultiChoiceModeListener
接口的类。通过接口的名字就可以猜到这个一个在多选模式激活时候的回调,在这里我们可以处理多选时的逻辑。来看一下AbsListView.MultiChoiceModeListener
里提供的几个方法:
class MultiChoiceListener implements AbsListView.MultiChoiceModeListener {
@Override
public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
Log.d(TAG, "onItemCheckedStateChanged");
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
Log.d(TAG, "onCreateActionMode");
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
Log.d(TAG, "onPrepareActionMode");
return true;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
Log.d(TAG, "onActionItemClicked");
return false;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
Log.d(TAG, "onDestroyActionMode");
}
}
完成上述的代码后,当我们长按listview的item时通过Log日志可以看到onCreateActionMode
onPrepareActionMode
onItemCheckedStateChanged
这三个方法执行了,
并且这时候手机的主界面也有了变化:
可以看到在Toolbar上方又多出来了一条和Toolbar类似的东西(两个bar的位置问题后面会说到),这就是ListView的ActionMode模式。
而当我们点击左上角的箭头时,这个bar就会消失,并且会执行MultiChoiceListener
中的onDestroyActionMode
方法。
这就是一个完整的listview多选功能的激活和取消的过程,我们可以通过在MultiChoiceListener
类中编写相关的逻辑实现listview的多选。
在res/menu文件夹中创建menu_multi_choice.xml文件
<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity">
<item android:id="@+id/action_delete" android:title="@string/action_delete" android:orderInCategory="100" android:icon="@mipmap/ic_menu_delete_36dp" app:showAsAction="always"/>
<item android:id="@+id/action_choose_all" android:title="@string/action_choose_all" android:orderInCategory="100" android:icon="@mipmap/ic_menu_edit_36dp" app:showAsAction="always"/>
</menu>
在这个菜单文件中加入了两个按钮,分别是删除和全选.
然后在layout文件夹中新建一个customt_view.xml的布局文件,用作actionmode上的自定义view。
<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/custom_title_root" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal">
<TextView android:id="@+id/title" android:maxLines="1" android:singleLine="true" android:layout_gravity="center_vertical" android:layout_centerVertical="true" android:textColor="#ccffffff" android:textAppearance="?android:attr/textAppearanceMediumInverse" android:layout_weight="1" android:layout_width="wrap_content" android:layout_height="wrap_content" />
<TextView android:id="@+id/selected_conv_count" android:maxLines="1" android:singleLine="true" android:textAppearance="?android:attr/textAppearanceLarge" android:textColor="#ccffffff" android:layout_gravity="center_vertical" android:paddingRight="10dip" android:layout_width="wrap_content" android:layout_height="wrap_content" />
</LinearLayout>
然后在onCreateActionMode中编写代码实现多选菜单。
在MultiChoiceListener类中声明几个成员变量:
private View mMultiSelectionbarView;
private TextView mSelectedItemCount;
private boolean allCheckedMode;
下面是OncreateActionMode中的代码:
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
Log.d(TAG, "onCreateActionMode");
allCheckedMode = false;
getMenuInflater().inflate(R.menu.menu_multi_choice, menu);
if (mMultiSelectionbarView == null) {
mMultiSelectionbarView = LayoutInflater.from(MainActivity.this).inflate(R.layout.custom_view, null, false);
mSelectedItemCount = (TextView) mMultiSelectionbarView.findViewById(R.id.selected_item_count);
}
mode.setCustomView(mMultiSelectionbarView);
((TextView) mMultiSelectionbarView.findViewById(R.id.title)).setText("选择项目");
mSelectedItemCount.setText(mAdapter.getCheckedItemCount() + "");
return true;
}
记得要return true
,否则不会有效果。mAdapter.getCheckedItemCount()可以返回当前选中的条目的数量。
之前说过ActionBar和ActionModeBar会重复出现,可以通过在AppTheme中配置windowActionModeOverlay为true,来解决,这样就不会出现两个actionbar的情况了:
<item name="windowActionModeOverlay">true</item>
激活actionMode
之后,再次点击listview
中的item项时,就会触发onItemCheckedStateChanged(ActionMode mode,int position,long id,boolean checked)
方法,可以在Adapter类中添加相关的方法当这个方法执行时,对item项进行相应的处理,如改变背景等。
下面是onItemCheckedStateChanged
方法的实现:
@Override
public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
int checkedCount = 0;
if (allCheckMode) {
if (checked) {
mAdapter.getItemState()[position] = 0;
} else {
mAdapter.getItemState()[position] = 1;
}
checkedCount = mAdapter.getCheckedItemCount();
} else {
if (checked) {
mAdapter.getItemState()[position] = 1;
} else {
mAdapter.getItemState()[position] = 0;
}
checkedCount = mAdapter.getCheckedItemCount();
}
mSelectedConvCount.setText(checkedCount + "");
mAdapter.notifyDataSetChanged();
}
为Adapter添加updateBackground方法,当item选中时,更新item的背景,在getView()
里执行,当notifyDatasetChanged方法被调用时,就会执行到这里刷新item的背景。
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
holder = new ViewHolder();
convertView = LayoutInflater.from(mContext).inflate(R.layout.list_item, parent, false);
holder.mTv = (TextView) convertView.findViewById(R.id.tv);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
//首先缓存原来的background
if (!isCachedBackground) {
isCachedBackground = true;
mBackground = convertView.getBackground();
}
updateBackground(position,convertView);
holder.mTv.setText(getItem(position));
return convertView;
}
private void updateBackground(int position,View convertView) {
if (getItemState()[position] == 1) {
convertView.setBackgroundColor(0xFFDFDFDF);
} else if (getItemState()[position] == 0){
convertView.setBackgroundDrawable(mBackground);
}
}
在onDestroyActionMode()中将状态重置:
@Override
public void onDestroyActionMode(ActionMode mode) {
Log.d(TAG, "onDestroyActionMode");
allCheckedMode = false;
mAdapter.unCheckAll();
}
onActionItemClicked
方法onActionItemClicked
用来处理ActionMode中菜单的点击事件,如删除和全选
下面是实现代码:
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
Log.d(TAG, "onActionItemClicked");
switch (item.getItemId()) {
case R.id.action_delete:
mAdapter.deleteSelectedItems();
mode.finish();
break;
case R.id.action_choose_all:
if (mAdapter.isAllChecked()) {
mAdapter.unCheckAll();
mListView.clearChoices();
mode.finish();
} else {
mAdapter.checkAll();
for (int i = 0; i < mAdapter.getCount(); i++) {
mListView.setSelection(i);
}
allCheckedMode = true;
}
mAdapter.notifyDataSetChanged();
mSelectedItemCount.setText(mAdapter.getCheckedItemCount() + "");
break;
default:
break;
}
return true;
}
至此,listview的多选已经基本实现。当长按item项时会激活actionmode,点击要删除的item将其选中,然后点击删除按钮就可以将所选中的item删除。也可以通过全选按钮来进行全选。
package com.chaoyang805.actionmodedemo;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import java.util.List;
/** * Created by chaoyang805 on 2015/11/5. */
public class MyAdapter extends BaseAdapter {
private List<String> mList;
private Context mContext;
/** * 用来记录item的状态,1表示选中,0表示未选中 */
private int[] mItemState;
private boolean isCachedBackground = false;
private Drawable mBackground;
private boolean mActionModeStarted = false;
public MyAdapter(Context context, List<String> list) {
mList = list;
mContext = context;
mItemState = new int[mList.size()];
for (int i = 0; i < mItemState.length; i++) {
mItemState[i] = 0;
}
}
public int[] getItemState() {
return mItemState;
}
@Override
public int getCount() {
return mList.size();
}
@Override
public String getItem(int position) {
return mList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
holder = new ViewHolder();
convertView = LayoutInflater.from(mContext).inflate(R.layout.list_item, parent, false);
holder.mTv = (TextView) convertView.findViewById(R.id.tv);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
//首先缓存原来的background
if (!isCachedBackground) {
isCachedBackground = true;
mBackground = convertView.getBackground();
}
updateBackground(position, convertView);
holder.mTv.setText(getItem(position));
return convertView;
}
public int getCheckedItemCount() {
int count = 0;
for (int i : mItemState) {
if (i == 1) count++;
}
return count;
}
private void updateBackground(int position,View convertView) {
if (getItemState()[position] == 1) {
convertView.setBackgroundColor(0xFFDFDFDF);
} else if (getItemState()[position] == 0){
convertView.setBackgroundDrawable(mBackground);
}
}
public void checkAll() {
for (int i = 0; i < mItemState.length; i++) {
mItemState[i] = 1;
}
}
public void unCheckAll() {
for (int i = 0; i < mItemState.length; i++) {
mItemState[i] = 0;
}
}
public boolean isAllChecked() {
for (int i : mItemState) {
if (i == 0) {
return false;
}
}
return true;
}
public void setActionModeState(boolean flag) {
mActionModeStarted = flag;
}
public boolean isActionModeStarted() {
return mActionModeStarted;
}
public void deleteSelectedItems() {
for (int i = mItemState.length - 1; i >= 0; i--) {
if (mItemState[i] == 1) {
mList.remove(i);
}
}
notifyDataSetChanged();
mItemState = new int[mList.size()];
for (int i : mItemState) {
i = 0;
}
}
public boolean isActionModeStart() {
return mActionModeStarted;
}
static class ViewHolder{
TextView mTv;
}
}