【解决自定义多选ListView乱序问题】
正常情况下,自定义带CheckedBox的ListView的时候,如果Items的数目超过一个屏幕,你会发现被checked的Item在屏幕滚动之后会出现乱序现象。
出现这种乱序现象的原因可以参考如下博文
http://haking.iteye.com/blog/1147404
如上述,Android为我们提供了一个ListView的缓冲机制,在屏幕滚动时,会重新利用被遮挡(即上一个屏幕的Items)的View进行更新显示。这也难怪我们的选择状态的显示会不尽人意。
既然知道了其原因,解决办法也就应运而生了。
【办法一】如网上所说,禁用android提供的缓冲机制,即在getView开头人为使得convertView变为null,从而强制为每一个item创建一个新的view用于显示。
但是当listview要显示的量比较大时,这个方法就显得有点臃肿不堪。
Bill今 早根据网上各位仁兄的方法,琢磨了个比较简单的解决办法(之所以简单,是因为这个方法只针对具有CheckedBox的ListView,而对于要保存 ImageView之类的ListView,除网上的讲解外,bill自己还没有想出更好的办法),不知道是否对大家有所帮助。
【办法二】既然我们因为数据量太大的原因确实有必要使用android自带的缓冲机制,那么禁用它看来是不现实了。现在问题的思考点就转到“在这种缓冲机制存在的情况下,如何能够正确地显示checked的状态?”,或者更加明白一点“在缓冲机制存在的情况下,CheckedBox的状态会发生改变,如何保存这些状态并在之后正确还原?”
Bill的思路如下:android要更新回收站中的item就让它更新吧,我用另外的空间记录每个CheckedBox之前的选中状态,等到要显示对应CheckedBox的时候再根据之前记录的状态手动设置CheckedBox的状态即可。
一下是本解决方案的Demo:
- <!-- checked listview item layout -->
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="horizontal" android:layout_width="fill_parent"
- android:layout_height="fill_parent">
- <CheckBox android:id="@+id/checked_box" android:layout_width="wrap_content"
- android:layout_height="wrap_content" android:focusable="false"></CheckBox>
- <TextView android:id="@+id/text_view" android:layout_width="wrap_content"
- android:layout_height="wrap_content"></TextView>
- </LinearLayout>
- <!-- main activity layout -->
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical" android:layout_width="fill_parent"
- android:layout_height="fill_parent">
- <Button android:id="@+id/bt_show_checked" android:layout_width="wrap_content"
- android:layout_height="wrap_content" android:text="哪些项被选中了?"></Button>
- <ListView android:id="@+id/lv_demo" android:layout_width="fill_parent"
- android:layout_height="fill_parent"></ListView>
- </LinearLayout>
- /**
- * 自定义CheckedListViewAdapter
- * 关键部分有所注释
- */
- package com.billhoo.study;
- import java.util.ArrayList;
- import java.util.List;
- import android.view.LayoutInflater;
- import android.view.View;
- import android.view.ViewGroup;
- import android.widget.BaseAdapter;
- import android.widget.CheckBox;
- import android.widget.TextView;
- // 自定义适配器,用于实现将CheckBox作为ListView的item
- public class CheckedListViewAdapter<ItemTy> extends BaseAdapter {
- // 状态数组,用于跟踪ListView中每一个CheckedBox的选中状态
- private ArrayList<Boolean> mCheckedStates = null;
- private LayoutInflater mInflater = null;
- private List<ItemTy> mItemList = null;
- public CheckedListViewAdapter(LayoutInflater inflater, List<ItemTy> list) {
- mCheckedStates = new ArrayList<Boolean>();
- this.mInflater = inflater;
- this.mItemList = list;
- // 初始化所有checked box选中状态为false
- for (int i = 0; i < mItemList.size(); ++i)
- mCheckedStates.add(false);
- }
- public ArrayList<Boolean> getCheckedState() {
- return mCheckedStates;
- }
- @Override
- public int getCount() {
- return mItemList.size();
- }
- @Override
- public Object getItem(int position) {
- return mItemList.get(position);
- }
- @Override
- public long getItemId(int position) {
- return position;
- }
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- ItemViewHolder holder = null;
- if (convertView == null) {
- holder = new ItemViewHolder();
- convertView = mInflater.inflate(R.layout.checked_listview_item, null);
- final View view = convertView;
- holder.checkBox = (CheckBox) view.findViewById(R.id.checked_box);
- holder.textView = (TextView) view.findViewById(R.id.text_view);
- convertView.setTag(holder);
- } else {
- holder = (ItemViewHolder) convertView.getTag();
- }
- // 每次getView的时候都手动设置位于当前屏幕中的checkedBox的选定状态。
- holder.checkBox.setChecked(mCheckedStates.get(position));
- holder.textView.setText(mItemList.get(position).toString());
- final int pos = position;
- // 当checked box被点击,即选定状态发生改变时,更新状态List
- holder.checkBox.setOnClickListener(new CheckBox.OnClickListener() {
- @Override
- public void onClick(View v) {
- CheckBox cb = (CheckBox) v;
- mCheckedStates.set(pos, cb.isChecked());
- }
- });
- return convertView;
- }
- protected class ItemViewHolder {
- public CheckBox checkBox = null;
- public TextView textView = null;
- }
- }
- /**
- * main activity
- */
- package com.billhoo.study;
- import java.util.ArrayList;
- import java.util.List;
- import android.app.Activity;
- import android.os.Bundle;
- import android.view.View;
- import android.widget.Button;
- import android.widget.ListView;
- import android.widget.Toast;
- public class CheckedListViewAdapterActivity extends Activity {
- private Button btShowCheckState = null;
- private ListView lvDemo = null;
- CheckedListViewAdapter<TestData> adapter = null;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- btShowCheckState = (Button) findViewById(R.id.bt_show_checked);
- lvDemo = (ListView) findViewById(R.id.lv_demo);
- }
- @Override
- public void onStart() {
- super.onStart();
- btShowCheckState.setOnClickListener(new Button.OnClickListener() {
- @Override
- public void onClick(View v) {
- String checkedItems = new String();
- ArrayList<Boolean> states = adapter.getCheckedState();
- for (int i = 0; i < states.size(); ++i) {
- if (states.get(i))
- checkedItems += i + " ";
- }
- Toast.makeText(getApplicationContext(), checkedItems,
- Toast.LENGTH_SHORT).show();
- }
- });
- }
- @Override
- public void onResume() {
- super.onResume();
- List<TestData> dataList = new ArrayList<TestData>();
- for (int i = 0; i < 20; ++i) {
- dataList.add(new TestData(i, "这是第" + i + "项数据"));
- }
- adapter = new CheckedListViewAdapter<TestData>(getLayoutInflater(),
- dataList);
- lvDemo.setAdapter(adapter);
- }
- class TestData {
- public TestData(Integer dataId, String msg) {
- this.dataId = dataId;
- this.msg = msg;
- }
- @Override
- public String toString() {
- return dataId + " " + msg;
- }
- private Integer dataId = null;
- private String msg = null;
- }
- }
以下是测试效果:
【未进行手动处理之前】
点选0、4两项
之后向下滚屏,发现并未勾选的9、14项却被勾选了
再滚回第一页,发现0、4项已经被取消,却勾上了第6项
【使用方法2之后】
照样勾选0、4项
之后向下滚屏,并未出现上述乱序情况
屏幕回滚并点击统计按钮,得到正确结果