做android开发久了 ,难免会经常使用ListView ,使用ListView久了 难免会碰到多选的问题,关于多选,做过的应该都了解,会出现选中混乱的问题。以前的解决版本就是使chekedBox不可获取焦点,然后通过点击listview 的item 实现多选,这样倒是勉强解决了,但总觉得很麻烦,另外与我的初衷也是不太符的,我想点的是checkbox,最后却强制让我点成了item,最重要的 如果我需要用onitemClick事件做其他的怎么办,所以想想还是有问题的,但当时也是时间较紧加上对listview加载机制不太了解,也就没再做过多的思考和优化,另外网上貌似也有一些其他的解决方案,因为没有仔细查过所以我也不太了解,暂不去管它。今天又有了这个需求,所以就抽了些时间对这个问题研究了一下。
对于解决此问题或者说其他任何问题,首要条件就是你要知道是什么造成的,那它到底是什么造成的呢? 说起来比较抽象,所以还是直接看代码吧
首先先看布局文件, 很简单
acivity layout 文件
<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" >
<Button
android:text="showCheckedItem"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="show"
/>
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/listview"
></ListView>
</LinearLayout>
接下来是ListView item 布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/cbTest"
/>
</LinearLayout>
OK,布局文件就这两个 下面看java代码
我先建一个Studnet实体类
package com.example.test;
public class Student {
public boolean isChecked;
public String name;
public int no;
public Student(String name,int no)
{
this.no = no;
this.name = name;
isChecked = false;
}
}
下面再建一个自定义的adapter
package com.example.test;
import java.util.ArrayList;
import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.Toast;
public class TestAdapter extends BaseAdapter{
private final String TAG = "TestAdapter";
private ArrayList<Student> list;
private Context context;
private LayoutInflater inflater;
private boolean isClick = false;
public TestAdapter(ArrayList<Student> list, Context context) {
super();
this.list = list;
this.context = context;
inflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return list.size();
}
@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return list.get(position);
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
final ViewHolder viewHolder;
if(convertView == null)
{
viewHolder = new ViewHolder();
convertView = inflater.inflate(R.layout.test_list_item_layout, null);
viewHolder.cbTest = (CheckBox)convertView.findViewById(R.id.cbTest);
convertView.setTag(viewHolder);
}
else
{
viewHolder = (ViewHolder)convertView.getTag();
}
final Student stu = list.get(position);
Log.d(TAG, stu.isChecked+"---"+position);
viewHolder.cbTest.setText(stu.name);
Log.d(TAG,"--"+position);
// viewHolder.cbTest.setChecked(stu.isChecked);
viewHolder.cbTest.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
// Student stu1 = (Student)(viewHolder.cbTest).getTag();
Log.e(TAG, stu.isChecked+"----"+"no:"+stu.no +"--"+position);
if(isChecked)
{
stu.isChecked = true;
}
else
{
stu.isChecked = false;
}
// Toast.makeText(context, stu.isChecked+"---"+position, Toast.LENGTH_SHORT).show();
}
});
viewHolder.cbTest.setChecked(stu.isChecked);
return convertView;
}
private class ViewHolder
{
public CheckBox cbTest;
}
}
看这里,貌似没有什么特别的,也确实没有什么特别的,但是这样确实就不会出现混乱的情况了,仔细观察就会发现, 关键就是这行代码 viewHolder.cbTest.setChecked(stu.isChecked);的位置 在我最初我编写的时候 ,都会把它放到添加监听器之前的位置,也就是setOnCheckedChangeListener这个方法的前面,这样的结果也就像上面说的那样,会出现混乱。但经过我的思考和尝试,我把它换了一个位置 就“神奇”的好 了。那这是为什么呢,简单来说就是为了使cbTest控件(也就是我们放到listview每个item中的checkbox控件)在每次调用setChecked()方法之前 更新它的 OnCheckedChangeListener。也许这时,还会奇怪为什么要这样呢?下面开始阐述 选择混乱造成的原因,以及为什么这样改变后就解决了。我觉得 如果要理解 我下面说的 首先要对listview item正常的加载以及优化后的加载机制比较了解。好,假设,你已经知道了。
首先我们应该知道造成混乱的根本原因就是 控件的复用,为什么我就不说了,了解加载机制自然就知道了,那我们为什么要复用呢,之所以要复用 ,是为了减少对象的创建,节约资源,但同时也是造成这个问题的根本原因。先回顾一下产生我们操作的过程。首先当我第一次打开界面不进行任何操作,先会加载能显示的item ,没有问题,但item加载完了,我们开始选择更改checkbox状态,也没问题,该选中的确实选中了,通过 输出 可以看到 item对于的Student(就是我上面创建的实体类)对象状态也改变了,一切貌似都很顺利,好,然后我开始滚动,当我们把我们选中的item滚动至消失时,然后再滚动回来,发现我们原来选中的item
又变回原来状态了,最最“诡异”的是就连我们更改了的Student对象的状态也变了,为什么呢? 仔细想想 ,首先当我们把我们选择的控件滚动至消失时,经过我们的优化,该控件还会被复用,就是说在再次执行getView方法时,这次的checkbox控件还是上次的那个控件,同样监听器也还是上次添加的那个监听器, 因为每次执行getView 方法都还会执行setChecked方法 ,同时还会执行监听器中的onCheckedChanged方法,而这个监听器 还是上次添加的那个监听器,里面的Student对象 也是上次操作的对象 这样就无形中又将我们改变了状态的Student对象的状态变了回来,所以当我再次滚动到我们选中的item位置时,就又变回原来的样子了。
以上说的就是按最初的写法为什么会出现混乱的原因,知道原因,那只要为什么改变了下写法就好了的问题,就很容易理解了,我先更新监听器,然后再执行setChecked方法
时我们每次操作的都是当下的Student对象 不妨碍其他item,所以也就不会混乱了。
说了一大堆,也不知道说明白了没有。。。。
下面是测试使用
package com.example.test;
import java.util.ArrayList;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ListView;
public class MainActivity extends Activity {
private ArrayList<Student> list = new ArrayList<Student>();
private ListView listView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView)findViewById(R.id.listview);
for(int i=0;i<30;i++)
{
Student stu =new Student("student"+i,i);
list.add(stu);
}
TestAdapter adapter =new TestAdapter(list, this);
listView.setAdapter(adapter);
}
public void show(View v)
{
for(Student stu:list)
{
if(stu.isChecked)
{
Log.d(this.getClass().getSimpleName(), stu.name+"被选中");
}
}
}
}
OK 就是这些了。 另外 说下 csdn的代码编辑器真难用