android 简单易用的ListView 实现多选的解决方案

来源:互联网 发布:黎明杀机淘宝好便宜 编辑:程序博客网 时间:2024/06/06 10:50

         做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);}@Overridepublic int getCount() {// TODO Auto-generated method stubreturn list.size();}@Overridepublic Object getItem(int position) {// TODO Auto-generated method stubreturn list.get(position);}@Overridepublic long getItemId(int position) {// TODO Auto-generated method stubreturn position;}@Overridepublic 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() {@Overridepublic 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;@Overrideprotected 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的代码编辑器真难用