1. 程式人生 > >ListView巢狀CheckBox滑動時CheckBox選中狀態錯亂

ListView巢狀CheckBox滑動時CheckBox選中狀態錯亂

轉載自:http://blog.csdn.net/qq_20521573/article/details/52655570

在討論這個問題之前應該先了解ListView的複用機制 
ListView複用的原理:ListView中的每一個Item顯示都需要Adapter呼叫一次getView的方法,這個方法會傳入一個convertView的引數,返回的View就是這個Item顯示的View。如果當Item的數量足夠大,再為每一個Item都建立一個View物件,必將佔用很多記憶體,建立View物件(mInflater.inflate(R.layout.lv_item, null);從xml中生成View,這是屬於IO操作)也是耗時操作,所以必將影響效能。Android提供了一個叫做Recycler(反覆迴圈器)的構件,就是當ListView的Item從上方滾出螢幕視角之外,對應Item的View會被快取到Recycler中,相應的會從下方生成一個Item,而此時呼叫的getView中的convertView引數就是滾出螢幕的Item的View,所以說如果能重用這個convertView,就會大大改善效能。 
如果一個螢幕最多顯示7個Item,當Item1滑出螢幕,此時Item1 的View被新增進Recycler中,相應的在下部要產生一個Item8,這時呼叫getView方法,convertView引數就是Item1 的View。 
ListView的複用雖然大大提升了效能,但是卻也帶來很多問題。比如在載入圖片時,由於下邊的item複用了上邊的item,造成下邊item剛加載出來時顯示的還是上邊被複用的item的圖片,等到這個新的item載入圖片完畢時才會正常顯示,這就是convert view複用造成listview圖片載入錯亂的問題。 
與上邊問題相似,在listview的item中存在CheckBox時也會由於複用convert view導致CheckBox的選中狀態錯亂,本片內容將解決由於複用導致CheckBox選中狀態錯亂的問題。 
先看下存在問題的效果圖 
這裡寫圖片描述

上圖中只選中了北京市和天津市,當下滑ListView時發現下邊的河南省和山東省也被選中了,再往下滑四川省和臺灣省也被選中。其實可以發現一個規律,每一屏都會有兩個條目被選中,其實這兩個被選中的條目就是因為複用了第一屏的兩個被選中的條目所導致的。 
先看下ListView沒有優化前的程式碼:

package com.example.edianzu.mycheckbox;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ListView;

import
java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { private ListView mListView; private List<String> mStringList; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mStringList=new
ArrayList<>(); mStringList.add("北京市");mStringList.add("天津市");mStringList.add("上海市");mStringList.add("重慶市"); mStringList.add("河北省");mStringList.add("山西省");mStringList.add("遼寧省");mStringList.add("河南省"); mStringList.add("山東省");mStringList.add("湖北省");mStringList.add("湖南省");mStringList.add("江西省"); mStringList.add("福建省");mStringList.add("陝西省");mStringList.add("四川省");mStringList.add("臺灣省"); mListView= (ListView) findViewById(R.id.lv_main); MyAdapter adapter=new MyAdapter(this,mStringList); mListView.setAdapter(adapter); } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

MainActivity中的程式碼為ListView適配資料和適配Adapter,不作過多解釋。

package com.example.edianzu.mycheckbox;

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.TextView;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by zhpan on 2016/9/24.
 */

public class MyAdapter extends BaseAdapter {

    List<String> mStringList;
    Context mContext;

    public MyAdapter(Context context, List<String> stringList) {
        mStringList = stringList;
        mContext=context;
    }

    @Override
    public long getItemId(int position) {
        return 0;
    }

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        MyViewHolder holder;
        if(convertView==null){
            convertView=View.inflate(mContext,R.layout.item,null);
            holder=new MyViewHolder();
            holder.mTextView= (TextView) convertView.findViewById(R.id.tv_item);
            holder.mCheckBox= (CheckBox) convertView.findViewById(R.id.cb_item);
            convertView.setTag(holder);
        }else {
            holder= (MyViewHolder) convertView.getTag();
        }

        holder.mTextView.setText(mStringList.get(position));

        return convertView;
    }

    @Override
    public int getCount() {
        return mStringList.size();
    }

    @Override
    public Object getItem(int position) {
        return null;
    }

    public static class  MyViewHolder {
        TextView mTextView;
        CheckBox mCheckBox;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66

在Adapter中只是複用了convertView,沒有對CheckBox做任何處理,那麼這樣寫的程式碼是存在上圖中的CheckBox選中狀態錯亂問題的。為了解決這個問題我們需要對CheckBox的選中狀態做下儲存,可以在Adapter中宣告一個Map集合用來儲存被選中的CheckBox。修改後程式碼如下:

package com.example.edianzu.mycheckbox;

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.TextView;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by zhpan on 2016/9/24.
 */

public class MyAdapter extends BaseAdapter {

    List<String> mStringList;
    Context mContext;
    private Map<Integer,Boolean> map=new HashMap<>();// 存放已被選中的CheckBox

    public MyAdapter(Context context, List<String> stringList) {
        mStringList = stringList;
        mContext=context;
    }

    @Override
    public long getItemId(int position) {
        return 0;
    }

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        MyViewHolder holder;
        if(convertView==null){
            convertView=View.inflate(mContext,R.layout.item,null);
            holder=new MyViewHolder();
            holder.mTextView= (TextView) convertView.findViewById(R.id.tv_item);
            holder.mCheckBox= (CheckBox) convertView.findViewById(R.id.cb_item);
            convertView.setTag(holder);
        }else {
            holder= (MyViewHolder) convertView.getTag();
        }

        holder.mTextView.setText(mStringList.get(position));
        holder.mCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if(isChecked==true){
                    map.put(position,true);
                }else {
                    map.remove(position);
                }
            }
        });

        if(map!=null&&map.containsKey(position)){
            holder.mCheckBox.setChecked(true);
        }else {
            holder.mCheckBox.setChecked(false);
        }

        return convertView;
    }

    @Override
    public int getCount() {
        return mStringList.size();
    }

    @Override
    public Object getItem(int position) {
        return null;
    }

    public static class  MyViewHolder {
        TextView mTextView;
        CheckBox mCheckBox;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83

針對這個問題我們子Adapter中加入了一個Map集合,其中Map的key用來儲存條被選中的checkbox的position,value用來儲存checkbox被選中。程式碼中還添加了checkbox的監聽事件,在監聽事件中判斷點選的checkbox是否被選中,如果被選中了則將position新增到集合,並設定狀態未true,否則就將該checkbox從集合中移除。然後通過if語句判斷集合中是否存在該checkbox,如果存在則證明是被選中的,遂將該checkbox設定為選中狀態setChecked(true),否則證明checkbox沒有選中則設定setChecked(false)。這樣就解決了checkbox選中狀態錯亂的問題。 
看下優化後的效果圖 
這裡寫圖片描述

優化後不會再出現錯亂問題了。