说到用ListView完成这个功能,很多人的第一想法是,通过维护一个被选中的集合,然后在adapter中通过判断是否被选中来改变不同的显示方式(我不止一次的看到用这种方式实现的多选)。
但是很多人不知道的是,ListView其实自带多选模式,使用这个模式可以很轻松的完成多选功能,先上效果图。
ListView在设计之初就考虑到了多选,所以ListView其实有四种模式,分别是CHOICE_MODE_NONE、CHOICE_MODE_SINGLE、CHOICE_MODE_MULTIPLE、CHOICE_MODE_MULTIPLE_MODAL,其中CHOICE_MODE_NONE是普通模式,CHOICE_MODE_SINGLE是单选模式,CHOICE_MODE_MULTIPLE、CHOICE_MODE_MULTIPLE_MODAL则都是多选模式,但是这两种有个区别,就是当模式为CHOICE_MODE_MULTIPLE_MODAL时,用户必须通过长按任意一个列表项来进入多选模式,否则不能进行多选。当然你也可以通过调用ListView的setItemChecked(int position, boolean value)的方式将他的某个列表项设置为选中来开启这个状态。我们选用的是CHOICE_MODE_MULTIPLE这个模式。
上代码,首先是用于标识列表选中与未选中状态的选择器lv_item_selecter.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/lv_checked" android:state_activated="true">item>
<item android:drawable="@color/lv_unchecked" android:state_activated="false">item>
selector>
然后是对应的颜色值color.xml
<resources>
<color name="lv_unchecked">#00ff00color>
<color name="lv_checked">#ff0000color>
resources>
需要注意的是因为ListView中被选中的item的状态标识是activated,所以我们的选择器设置的状态是state_activated,用state_selected或者state_pressed啥的都是无效的。
接着,我们将这个selector设置给我们ListView的item的布局
item.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/lv_item_selecter"
android:orientation="vertical" >
<TextView
android:id="@+id/txt"
android:layout_width="match_parent"
android:layout_height="60dp"
android:gravity="center" />
LinearLayout>
布局文件activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<Button
android:id="@+id/btn_multichoice"
android:layout_width="120dp"
android:layout_height="60dp"
android:text="开启多选" />
<Button
android:id="@+id/btn_selectall"
android:layout_width="120dp"
android:layout_height="60dp"
android:text="全选" />
<Button
android:id="@+id/btn_unselectall"
android:layout_width="120dp"
android:layout_height="60dp"
android:text="取消全选" />
LinearLayout>
<TextView
android:id="@+id/txt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20sp" />
<ListView
android:id="@+id/lv"
android:layout_width="match_parent"
android:layout_height="match_parent" >
ListView>
LinearLayout>
然后是适配器MyAdapter.java
import java.util.List;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
/**
* @author lhq
*/
public class MyAdapter extends BaseAdapter {
private List list;
private LayoutInflater inflater;
public MyAdapter(List list, Context context) {
super();
this.list = list;
this.inflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int position) {
return list.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (null == convertView) {
holder = new ViewHolder();
convertView = inflater.inflate(R.layout.item, null);
holder.txt = (TextView) convertView.findViewById(R.id.txt);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.txt.setText(list.get(position));
return convertView;
}
class ViewHolder {
TextView txt;
}
}
接下来就是我们的activity
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.os.Bundle;
import android.util.SparseBooleanArray;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity {
private ListView lv;
private Button multiChoiceBtn, selectAllBtn, unSelectAllBtn;
private TextView txt;
private List list = new ArrayList();
/**
* 标记当前是否处于多选状态
*/
private boolean isMultiChoice = false;
private MyAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initVariables();
initView();
initEvent();
}
/**
* 初始化数据
*/
private void initVariables() {
for (int i = 0; i < 20; i++) {
list.add("我是条目" + i);
}
adapter = new MyAdapter(list, MainActivity.this);
}
/**
* 初始化控件
*/
private void initView() {
lv = (ListView) findViewById(R.id.lv);
multiChoiceBtn = (Button) findViewById(R.id.btn_multichoice);
selectAllBtn = (Button) findViewById(R.id.btn_selectall);
unSelectAllBtn = (Button) findViewById(R.id.btn_unselectall);
txt = (TextView) findViewById(R.id.txt);
lv.setAdapter(adapter);
}
/**
* 设置控件的事件
*/
private void initEvent() {
lv.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView> arg0, View arg1, int arg2, long arg3) {
if (isMultiChoice) {
updateTxt();
} else {
Toast.makeText(MainActivity.this, "点击了" + arg2, Toast.LENGTH_SHORT).show();
}
}
});
selectAllBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
for (int i = 0; i < adapter.getCount(); i++) {
lv.setItemChecked(i, true);
updateTxt();
}
}
});
unSelectAllBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
// 清除所有选中的数据
lv.clearChoices();
// 更新ListView
adapter.notifyDataSetInvalidated();
updateTxt();
}
});
multiChoiceBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
if (isMultiChoice) {
// 退出多选模式
Toast.makeText(MainActivity.this, "退出多选", Toast.LENGTH_SHORT).show();
multiChoiceBtn.setText("开启多选");
// 清除所有选中的数据
lv.clearChoices();
updateTxt();
// 更新ListView
adapter.notifyDataSetInvalidated();
lv.postDelayed(new Runnable() {
@Override
public void run() {
// 将ListView的模式改成普通模式
lv.setChoiceMode(ListView.CHOICE_MODE_NONE);
}
}, 100);
isMultiChoice = false;
} else {
// 切换到多选模式
Toast.makeText(MainActivity.this, "切换到多选", Toast.LENGTH_SHORT).show();
multiChoiceBtn.setText("退出多选");
// 开启多选模式
lv.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
isMultiChoice = true;
}
}
});
}
/**
* 更新TextView上的内容
*/
private void updateTxt() {
SparseBooleanArray array = lv.getCheckedItemPositions();
if (null != array && array.size() > 0) {
String str = "";
// 遍历选中的结果集
for (int i = 0; i < array.size(); i++) {
int key = array.keyAt(i);
if (array.get(key)) {
str += key + ",";
}
}
txt.setText("选中了" + str);
} else {
txt.setText("没有选中任何项目");
}
}
}
可以看到,我们是通过调用ListView的setChoiceMode这个方法来切换多选状态和普通状态的。通过clearChoices()这个方法来清除所有选中的item,记得清空以后要调用adapter的notifyDataSetInvalidated()来更新ListView的状态,因为clearChoices()只是清除了选中的item的状态,并没有去更新item的view,之前选中的item的选中效果会依旧存在,需要我们主动去更新。
我们可以通过getCheckedItemPositions()方法来获取一个SparseBooleanArray,我们可以遍历他获取所有被选中item的坐标
还有一个注意点,大家可以看到,当我退出多选模式的时候,我是在通过postDelayed的方式让setChoiceMode方法延时执行
if (isMultiChoice) {
// 退出多选模式
Toast.makeText(MainActivity.this, "退出多选", Toast.LENGTH_SHORT).show();
multiChoiceBtn.setText("开启多选");
// 清除所有选中的数据
lv.clearChoices();
updateTxt();
// 更新ListView
adapter.notifyDataSetInvalidated();
lv.postDelayed(new Runnable() {
@Override
public void run() {
// 将ListView的模式改成普通模式
lv.setChoiceMode(ListView.CHOICE_MODE_NONE);
}
}, 100);
isMultiChoice = false;
这是因为我在实际使用中发现,如果你在notifyDataSetInvalidated(),或者是setChecked()等代码后面直接调用setChoiceMode(ListView.CHOICE_MODE_NONE);ListView的界面将不会更新。所以我这边用了个比较取巧的方式,让这段代码延迟执行,等到界面更新完成后才调用,就不会出现这种问题了(如果有更好的方式欢迎提出,这个问题纠结了我好久)。
ps:在这个模式下,item的点击事件也能正常执行,所以在处理点击事件的时候要考虑到这一点