postition of ticked (checked) checkbox changes aft

2020-07-27 17:13发布

问题:

I am trying to apply filter on my custom adapter which extends BaseAdapter, in which I am facing some problems, after I filter input based on the text in EditText and check the CheckBox to select one value and if I erase the text in the EditText to search for some other thing the position of the checked checkbox changes automatically as you can see in the image attached below. I am currently facing two problems

1 - Position of the ticked checkbox changes automatically

2 - If I type in uppercase in the EditText the ListView goes blank.

public class MyCustomAdapter extends BaseAdapter implements Filterable {

Context mContext;
private LayoutInflater mInflater;
SparseBooleanArray mSparseBooleanArray;
private ArrayList<Map<String,String>> mAdapData = new ArrayList<Map<String, String>>();
private ArrayList<Map<String,String>> mOriginalData = new ArrayList<Map<String, String>>();

public MyCustomAdapter(Context mContext) {
    mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    mSparseBooleanArray = new SparseBooleanArray();
}

public ArrayList<String> getCheckedItems() {
    ArrayList<String> mTempArry = new ArrayList<String>();
    for (int i = 0; i < mAdapData.size(); i++) {
        if (mSparseBooleanArray.get(i)) {
            Map<String, String> map = (Map<String, String>) mAdapData.get(i);
            final String numbr = map.get("Phone").toString();
            mTempArry.add(numbr);
        }
    }
    return mTempArry;
}

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

public void addItem(String paramString1, String paramString2) {
    Map<String, String> NameNumber = new HashMap<String, String>();
    NameNumber.put("Name", paramString1);
    NameNumber.put("Phone", paramString2);
    this.mAdapData.add(NameNumber);
    this.mOriginalData.add(NameNumber);
    notifyDataSetChanged();
}

@SuppressWarnings("unchecked")
public Object getItem(int paramInt) {
    return (ArrayList<Map<String, String>>) this.mAdapData.get(paramInt);
}

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

@Override
public View getView(final int paramInt, View paramView, ViewGroup paramViewGroup) {

    ViewHolder viewHolder;

    if (paramView == null) {
        viewHolder = new ViewHolder();
        paramView = mInflater.inflate(R.layout.multiplecontactview, null);
        viewHolder.tvName = (TextView) paramView.findViewById(R.id.txtContactName);
        viewHolder.tvNumber = (TextView) paramView.findViewById(R.id.txtContactNumber);
        viewHolder.cb = (CheckBox) paramView.findViewById(R.id.checkBox1);
        viewHolder.cb.setTag(paramInt);
        viewHolder.cb.setChecked(mSparseBooleanArray.get(paramInt));
        viewHolder.cb.setOnCheckedChangeListener(mCheckedChangeListener);
        viewHolder.tvName.setTextColor(Color.BLACK);
        viewHolder.tvNumber.setTextColor(Color.BLACK);
        for (int i = 0; i < mAdapData.size(); i++) {
            Map<String, String> map = (Map<String, String>) mAdapData.get(paramInt);
            final String name = map.get("Name").toString();
            final String numbr = map.get("Phone").toString();
            viewHolder.tvName.setText(name);
            viewHolder.tvNumber.setText(numbr);
        }
        paramView.setTag(viewHolder);
    } else {
        viewHolder = (ViewHolder) paramView.getTag();
    }       
    return paramView;
}

OnCheckedChangeListener mCheckedChangeListener = new OnCheckedChangeListener() {
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        mSparseBooleanArray.put((Integer) buttonView.getTag(), isChecked);
    }
};

public static class ViewHolder {
    TextView tvName;
    TextView tvNumber;
    CheckBox cb;
}

@Override
public Filter getFilter() {
    return new MyContactFilter();
}

@SuppressLint("DefaultLocale")
private class MyContactFilter extends Filter {

    @Override
    protected FilterResults performFiltering(CharSequence constraint) {
        FilterResults results = new FilterResults();
        ArrayList<Map<String, String>> mFilteredData = new ArrayList<Map<String, String>>();

        if (!TextUtils.isEmpty(constraint)) { 
            for(int i = 0; i < mAdapData.size(); i++) {
                Map<String, String> map = (Map<String, String>) mAdapData.get(i);
                final String names = map.get("Name").toString();
                final String numbr = map.get("Phone").toString();
                if(names.toLowerCase().contains(constraint.toString().toLowerCase())) {
                    Map<String, String> FilNameNumber = new HashMap<String, String>();
                    FilNameNumber.put("Name", names);
                    FilNameNumber.put("Phone", numbr);
                    mFilteredData.add(FilNameNumber);
                }
            }
            results.values = mFilteredData;
            results.count = mFilteredData.size();

        } else {
            synchronized (mOriginalData) {
                results.values = mOriginalData;
                results.count = mOriginalData.size();
            }
        }
        return results;
    }

    @SuppressWarnings("unchecked")
    @Override
    protected void publishResults(CharSequence cs, FilterResults fr) {
        mAdapData = (ArrayList<Map<String, String>>) fr.values;
        notifyDataSetChanged();
    }
}
}

回答1:

If you look at your code more attentively, concretely on the getView(), you'll see that you create ViewHolder object every time independently of convertView presence.

This is wrong using of ViewHolder pattern. Instead of creating it everytime, create it only when convertView is null and set it as the tag of convertView by setTag(), otherwise get the reference to ViewHolder using getTag(). Just Like this:

 ViewHolder viewHolder;

    if (paramView == null) {
        paramView = mInflater.inflate(R.layout.multiplecontactview, null);
        viewHolder = new ViewHolder();
        viewHolder.tvName = (TextView) paramView.findViewById(R.id.txtContactName);
        viewHolder.tvNumber = (TextView) paramView.findViewById(R.id.txtContactNumber);
        viewHolder.cb = (CheckBox) paramView.findViewById(R.id.checkBox1);
        paramView.setTag(viewHolder);
    } else {
        viewHolder = (ViewHolder) paramView.getTag();
    }

    viewHolder.cb.setTag(paramInt);
    viewHolder.cb.setChecked(mSparseBooleanArray.get(paramInt));
    viewHolder.cb.setOnCheckedChangeListener(mCheckedChangeListener);
    viewHolder.tvName.setTextColor(Color.BLACK);
    viewHolder.tvNumber.setTextColor(Color.BLACK);
    for (int i = 0; i < mAdapData.size(); i++) {
        Map<String, String> map = (Map<String, String>) mAdapData.get(paramInt);
        final String name = map.get("Name").toString();
        final String numbr = map.get("Phone").toString();
        viewHolder.tvName.setText(name);
        viewHolder.tvNumber.setText(numbr);
    }
    return paramView;

Also in your preformFiltering() try to change this snippet:

if (names.toLowerCase().contains(cs))

to

if (names.toLowerCase().contains(cs.toString().toLowerCase()))

Update

There is some strange snippet of code:

OnCheckedChangeListener mCheckedChangeListener = new OnCheckedChangeListener() {
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        mSparseBooleanArray.put((Integer) buttonView.getTag(), isChecked);
    }
};

It means that if you first time check the list item which is on the 0 position in the list, than SparseArray indicates that the first item of the ListView should always have a checked box, because you don't uncheck it anywhere in you code, don't set the value to false but do it only when user manually uncheck the checkbox.



回答2:

Get rid of complex mappings to track properties, store the data in a class like this, along with checked state:

public class Data {
 private final String text;
 private boolean checked;

 public Data(String text){
   this.text = text;
   this.checked = false;
 }

 public void setChecked(boolean b){
   this.checked = b;
 }

 public boolean isChecked(){
   return this.checked;
 }

 public String getText(){
   return this.text;
 }
}

Inside getView() , set the checkbox according to data state, add listener to modify data state:

final Data data = getItem(position);

cb.setOnCheckedChangeListener(null); //important 
cb.setChecked(data.isChecked());
cb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        data.setChecked(isChecked);
    }
});

About filtering:

names.toLowerCase().contains(cs)

Contains does a case sensitive match, on char values. try names.toLowerCase().contains(cs.toLowerCase())