Android listview with checkboxes not behaving as e

2019-03-06 04:32发布

问题:

This is regarding the recycling issue. I am using a custom adapter to populate the list view. In the custom row there is an image view, two text boxes and a check box. The all the elements get populated but the check box is not populated correctly.

Inside the getView() I perform a condition and if the condition is true I set the check box to enable state. This works fine but with the correct check box which is ticked, there are some other check boxes getting ticked as well. I went through many stack overflow similar questions but was unable to find an answer. Any help is greatly appreciated.

Below is my adapter class:

public class LocationsListAdapter extends BaseAdapter {

    List<Locations> data;
    Context context;
    Locations userSelectedLocation;
    private SharedPreferences locationPreferences;
    private SharedPreferences.Editor locationPrefsEditor;

    public LocationsListAdapter(List<Locations> data, Context c) {
        this.data = data;
        this.context = c;
    }

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

    @Override
    public Object getItem(int position) {
        // TODO Auto-generated method stub
        return data.get(position);
    }

    @Override
    public long getItemId(int position) {
        // TODO Auto-generated method stub
        return position;
    }

    @SuppressWarnings("static-access")
    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {


        ViewHolder holder = null;
        Log.v("ConvertView", String.valueOf(position));

        if (convertView == null) {
            LayoutInflater vi = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = vi.inflate(R.layout.find_your_location_row, null);

            holder = new ViewHolder();
            holder.LocationImage = (SmartImageView) convertView.findViewById(R.id.loca_row_image);
            holder.locationName = (TextView) convertView.findViewById(R.id.txt_loca_name);
            holder.LocationDescription = (TextView) convertView.findViewById(R.id.txt_loca_desc);
            holder.locationCheckText = (TextView) convertView.findViewById(R.id.txt_check);
            holder.locationCheck = (CheckBox) convertView.findViewById(R.id.location_check);
            holder.locationCheck.setOnCheckedChangeListener(myCheckChangList);
            convertView.setTag(holder);

            locationPreferences = context.getSharedPreferences("locationPrefs", context.MODE_PRIVATE);
            locationPrefsEditor = locationPreferences.edit();
            String locationID  = locationPreferences.getString("locationID", "");

            try {
                if(locationID.contains(String.valueOf(data.get(position).getLocationID()))){
                    holder.locationCheck.setChecked(true);
                }
            } catch (Exception e) {
                Log.e("Fatal", " Exception");
            }


             holder.locationCheck.setOnClickListener( new OnClickListener() {

                    @Override
                    public void onClick(View v) {
                        userSelectedLocation = data.get(position);
                        locationPreferences = context.getSharedPreferences("locationPrefs", context.MODE_PRIVATE);
                        locationPrefsEditor = locationPreferences.edit();
                        String userSelectedLocationID = userSelectedLocation.getLocationID();
                        locationPrefsEditor.clear();
                        locationPrefsEditor.putString("locationID", userSelectedLocationID);
                        locationPrefsEditor.commit();
                        Intent intent = new Intent(context, HomeScreen.class);
                        context.startActivity(intent);
                        Log.e("Check Box ", "Clicked");
                    }
            });

        }else {
            holder = (ViewHolder) convertView.getTag();
           }

        final Locations location = data.get(position);

        holder.LocationImage.setImageUrl(location.getImagePath());
        holder.locationName.setText(location.getLocationName());
        holder.LocationDescription.setText(location.getLocationDescription());

        return convertView;
    }

    @Override
    public void unregisterDataSetObserver(DataSetObserver observer) {
        if (observer != null) {
            super.unregisterDataSetObserver(observer);
        }
    }

    protected class ViewHolder {
        protected SmartImageView LocationImage;
        protected TextView locationName;
        protected TextView LocationDescription;
        protected TextView locationCheckText;
        protected CheckBox locationCheck ;
    }

    OnCheckedChangeListener myCheckChangList = new OnCheckedChangeListener() {
        public void onCheckedChanged(CompoundButton buttonView,
                boolean isChecked) {
            Log.e("checked", "");
        }
    };
}

回答1:

I believe your problem lies in your if/else statement.

If convertView is null then you create a new view and populate it with all the correct data but if it is not null (i.e it is a recycled view), you simply return the exact same view back from the tag. You do not do anything with the view regarding setting its properties so it maintains the state it already had (some of which were checked)

Set your properties after you have a valid viewHolder.

You currently have this model.

if (convertView == null) {
    //Your code to create the view
    // Your code to set the view properties
}else {
    holder = (ViewHolder) convertView.getTag();
}

Change it to this (move the code that sets the view properties outside the if/else so that the view properties can be set for all views regardless of whether they are recycled.

 if (convertView == null) {
        LayoutInflater vi = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        convertView = vi.inflate(R.layout.find_your_location_row, null);

        holder = new ViewHolder();
        holder.LocationImage = (SmartImageView) convertView.findViewById(R.id.loca_row_image);
        holder.locationName = (TextView) convertView.findViewById(R.id.txt_loca_name);
        holder.LocationDescription = (TextView) convertView.findViewById(R.id.txt_loca_desc);
        holder.locationCheckText = (TextView) convertView.findViewById(R.id.txt_check);
        holder.locationCheck = (CheckBox) convertView.findViewById(R.id.location_check);
        convertView.setTag(holder);
    } else {
        holder = (ViewHolder) convertView.getTag();
    }

    Locations location = data.get(position);

    holder.LocationImage.setImageUrl(location.getImagePath());
    holder.locationName.setText(location.getLocationName());
    holder.LocationDescription.setText(location.getLocationDescription());
    boolean ShouldBoxBeChecked = //Insert check for the current box here
    holder.locationCheck.setChecked(ShouldBoxBeChecked);


回答2:

You have to do something like this at start make an array list and store state as false

 private ArrayList<Boolean> itemChecked = new ArrayList<Boolean>();    

  for (int i = 0; i < this.getCount(); i++) {
            itemChecked.add(i, false); // initializes all items value with false
        }

and whenever you click just change the state and populate the list with updated values everytime.



回答3:

OnClickListener() is being declared only for the first couple of list items, and is using the same "position" for all the rest of the items.

One option that can work is that you can set the position in the tag of each checkbox in the list and get it in your OnClickListener from the v param



回答4:

locationPreferences = context.getSharedPreferences("locationPrefs", context.MODE_PRIVATE);
    locationPrefsEditor = locationPreferences.edit();
    String locationID  = locationPreferences.getString("locationID", "");

    try {
        if(locationID.contains(String.valueOf(data.get(position).getLocationID()))){
            holder.locationCheck.setChecked(true);
        }
    } catch (Exception e) {
        Log.e("Fatal", " Exception");
    }

This part of code is only executed, if your convertView == null. You are not doing setChecked when your convertView != null, that means when you reuse view, it will not be checked correctly



回答5:

Here , i have a solution for you.

Basically i have created two array list of type string. When you check a checkbox, the corresponding textview value ( or whatever value you can store correspoding to that listview item) is stored in it the list CHECKEDLIST. In the other list ALLVALUES, all the values are stored.

On button click i match both the lists and if a match occurs, then i remove that value from ALLVALUES and call notifydatasetchanged

package com.example.test;

import java.io.File;
import java.util.ArrayList;


import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.os.Environment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.CompoundButton.OnCheckedChangeListener;

public class MainActivity extends Activity implements OnClickListener {
    private ArrayList<String> checkedIndices = new ArrayList<String>();
    ArrayList<String> list = new ArrayList<String>();
    AScustomadapter adapter;
int count = 0;
private String[] vidNames = {"a","b","c","d","e","f","g","h","i"};
private ListView myList;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Button b1 = (Button) findViewById(R.id.button1);
    b1.setOnClickListener(this);



    for (int i = 0; i < vidNames.length; i++) {
        list.add(vidNames[i]);
    }

    myList = (ListView)findViewById(R.id.list);
    adapter = new AScustomadapter(getApplicationContext(), list);
    myList.setAdapter(adapter);
}

@Override
public void onClick(View v) {
    if(v.getId() == R.id.button1) {


        for (int i = 0; i < checkedIndices.size(); i++) {
            for(int j = 0; j <list.size();j++){
            if(list.get(j).contains(checkedIndices.get(i))){
                list.remove(j);
            }
            }
        }
        adapter.notifyDataSetChanged();



    } 

}

public class AScustomadapter extends BaseAdapter {

private ArrayList<String> mListItems;
private LayoutInflater mLayoutInflater;
int i = 0;

public AScustomadapter(Context context, ArrayList<String> arrayList) {
    mListItems = arrayList;
    //get the layout inflater
    mLayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}

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

@Override
public Object getItem(int i) {
    return list.get(i);
}

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

public int getTotalCheckedCount() {
    return checkedIndices.size();
}

@Override
public View getView(final int position, View view, ViewGroup viewGroup) {
    ViewHolder holder;

    if (view == null) {
        holder = new ViewHolder();

        view = mLayoutInflater.inflate(R.layout.list_item, null);
        holder.itemName = (TextView) view.findViewById(R.id.list_item_text_view);
        holder.cb1 = (CheckBox) view.findViewById(R.id.checkBox1);

        view.setTag(holder);
    } else {
        holder = (ViewHolder)view.getTag();
    }

    final String stringItem = mListItems.get(position);

    if (stringItem != null) {
        if (holder.itemName != null) {
            holder.itemName.setText(stringItem);
        }
    }

    holder.cb1.setOnClickListener(new OnClickListener() {

      @Override
      public void onClick(View v) {
                //is chkIos checked?
        if (((CheckBox) v).isChecked()) {
            if(!checkedIndices.contains(getItem(index).toString()))
                checkedIndices.add(getItem(index).toString());          }
        else {
            checkedIndices.remove(getItem(index).toString());

        }
          //case 2

      }
    });

    if(checkedIndices.contains((Integer)position)) {
        holder.cb1.setChecked(true);
    } else {
        holder.cb1.setChecked(false);
    }

    //this method must return the view corresponding to the data at the specified position.
    return view;
}

private class ViewHolder {

    protected TextView itemName;
    protected CheckBox cb1;

}
  }


 }


回答6:

You need to save CheckBox state in an Array or something similar when they are checked/un-checked in order to remember state and at the end of the getView(...) method, need to set state for CheckBox depending on array values.

Have a look Checkbox not working properly with ListView link for more details.