Maintaining checkbox states in listview with CursorAdapter
Asked Answered
C

3

5

For my Android project, I have a listview which has a checkbox for every item. The data is loaded from an SQLite database by using a CursorAdapter class. However, whenever I scroll, the checkbox positions will get moved and get carried down to the next part of the listview. How can I fix this problem? GIF of my CheckBox Problem Here's my Cursor Adapter Class:

public class VocabCursorAdapter extends CursorAdapter {

private static final int DIFFICULT = 0;
private static final int FAMILIAR = 1;
private static final int EASY = 2;
private static final int PERFECT = 3;

public VocabCursorAdapter(Context context, Cursor c, int flags) {
    super(context, c, 0);
}

@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
    return LayoutInflater.from(context).inflate(R.layout.item_vocab, parent, false);
}

@Override
public void bindView(View view, Context context, Cursor cursor) {
    // Find fields to populate in inflated template
    TextView tvVocabName = (TextView) view.findViewById(R.id.vocabName);
    TextView tvVocabDefinition = (TextView) view.findViewById(R.id.vocabDefinition);
    ImageView tvVocabLevel = (ImageView) view.findViewById(R.id.vocabLevel);
    // Extract properties from cursor
    String vocab = cursor.getString(cursor.getColumnIndexOrThrow(VocabDbContract.COLUMN_NAME_VOCAB));
    String definition = cursor.getString(cursor.getColumnIndexOrThrow(VocabDbContract.COLUMN_NAME_DEFINITION));
    int level = cursor.getInt(cursor.getColumnIndexOrThrow(VocabDbContract.COLUMN_NAME_LEVEL));
    // Populate fields with extracted properties
    tvVocabName.setText(vocab);
    tvVocabDefinition.setText(definition);

    if (level == DIFFICULT) {
        tvVocabLevel.setImageResource(R.drawable.level_bars_difficult);
        tvVocabLevel.setTag(DIFFICULT);
    }
    else if (level == FAMILIAR) {
        tvVocabLevel.setImageResource(R.drawable.level_bars_familiar);
        tvVocabLevel.setTag(FAMILIAR);
    }
    else if (level == EASY) {
        tvVocabLevel.setImageResource(R.drawable.level_bars_easy);
        tvVocabLevel.setTag(EASY);
    }
    else if (level == PERFECT) {
        tvVocabLevel.setImageResource(R.drawable.level_bars_perfect);
        tvVocabLevel.setTag(PERFECT);
    }
}

And here's my list item xml, item_vocab.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:longClickable="true">

<ImageView
    android:layout_width="36sp"
    android:layout_height="36sp"
    android:id="@+id/vocabLevel"
    android:layout_gravity="right"
    android:src="@drawable/level_bars"
    android:scaleType="fitXY"
    android:contentDescription="@string/vocab_level"
    android:layout_centerVertical="true"
    android:layout_toLeftOf="@+id/editCheckbox"
    android:layout_toStartOf="@+id/editCheckbox"/>

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textAppearance="?android:attr/textAppearanceLarge"
    android:text="Large Text"
    android:id="@+id/vocabName"
    android:layout_alignParentTop="true"
    android:layout_alignParentLeft="true"
    android:layout_alignParentStart="true"
    android:layout_toLeftOf="@+id/vocabLevel"
    android:layout_toStartOf="@+id/vocabLevel"/>

<TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:textAppearance="?android:attr/textAppearanceSmall"
    android:text="Small Text"
    android:id="@+id/vocabDefinition"
    android:layout_alignParentLeft="true"
    android:layout_alignParentStart="true"
    android:layout_toLeftOf="@+id/vocabLevel"
    android:layout_toStartOf="@+id/vocabLevel"
    android:layout_below="@id/vocabName"/>

<CheckBox
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/editCheckbox"
    android:layout_centerVertical="true"
    android:layout_alignParentRight="true"
    android:layout_alignParentEnd="true"/>

</RelativeLayout>

And here's my xml which contains a listview

<RelativeLayout 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"
            tools:context=".controller.MyVocab"
            android:paddingLeft="5dp">

<ListView
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:id="@+id/mVocabList"
    android:layout_alignParentTop="true"
    android:layout_alignParentLeft="true"
    android:layout_alignParentStart="true"/>

<TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@string/empty_text_view"
    android:id="@android:id/empty"
    android:layout_alignParentTop="true"
    android:layout_alignParentLeft="true"
    android:layout_alignParentStart="true"/>

</RelativeLayout>

I have looked at a lot of different solutions on StackOverflow, but I wasn't able to successfully do it in my own app. For an example, this post has a similar problem, but its solution used getView and I had trouble understanding how to implement it with newView and bindView instead.

And some other solutions might be examples where a cursoradapter is not involved. Any help is much appreciated, thanks a lot! Edit #1: After incorporating Phan's changes, the checkbox states get resets to false rather than keeping its states when I scroll the listview (See GIF).

Corabella answered 4/5, 2016 at 21:24 Comment(0)
I
3

Reason : ListView re-uses the views.

Solution :

class VocabCursorAdapter extends CursorAdapter {
    List<Integer> selectedItemsPositions;//to store all selected items position

    public VocabCursorAdapter(Context context, Cursor c,int flags) {
        super(context, c,0);
        selectedItemsPositions = new ArrayList<>();
    }

    @Override
    public View newView(Context context, Cursor cursor, ViewGroup viewGroup) {
        View view = LayoutInflater.from(context).inflate(R.layout.item_vocab, viewGroup, false);
        CheckBox box = (CheckBox) view.findViewById(R.id.editCheckbox);
        box.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
                int position = (int) compoundButton.getTag();
                if (b) {
                    //check whether its already selected or not
                    if (!selectedItemsPositions.contains(position))
                        selectedItemsPositions.add(position);
                } else {
                    //remove position if unchecked checked item
                    selectedItemsPositions.remove((Object) position);
                }
            }
        });
        return view;
    }

    @Override
    public void bindView(View view, Context context, Cursor cursor) {

        //your other stuff

        CheckBox box = (CheckBox) view.findViewById(R.id.editCheckbox);
        box.setTag(cursor.getPosition());

        if (selectedItemsPositions.contains(cursor.getPosition()))
            box.setChecked(true);
        else
            box.setChecked(false);
    }
}
Incorporation answered 5/5, 2016 at 7:12 Comment(0)
M
1

Try this

public class VocabCursorAdapter extends CursorAdapter { 

   private ArrayList<Boolean> itemChecked = new ArrayList<Boolean>(); // array list for store state of each checkbox

   public VocabCursorAdapter(Context context, Cursor c, int flags) {

       for (int i = 0; i < c.getCount(); i++) { // c.getCount() return total number of your Cursor
            itemChecked.add(i, false); // initializes all items value with false
       }
   }

    @Override
    public void bindView(View view, Context context, Cursor cursor) {

        ...
        final int position = cursor.getPosition(); // get position by cursor

        CheckBox checkBox = (CheckBox) view.findViewById(R.id.editCheckbox);
        checkBox.setOnClickListener(new OnClickListener() {
                public void onClick(View v) {

                    if (itemChecked.get(position) == true) { // if current checkbox is checked, when you click -> change it to false
                        itemChecked.set(position, false);
                    } else {
                        itemChecked.set(position, true);
                    }
                }
       });

       checkBox.setChecked(itemChecked.get(position)); // set the checkbox state base on arraylist object state
       Log.i("In VocabCursorAdapter","position: "+position+" - checkbox state: "+itemChecked.get(position));
    }
}
Monotheism answered 5/5, 2016 at 2:0 Comment(5)
Hello Phan, thanks a lot for your help! I've incorporated your changes, however, the checkbox states now gets reset to false/empty whenever I scroll it to a new view rather than keeping its state.Corabella
I've attached the GIF after I incorporated your changes in the question.Corabella
@CharlesLi I have add 1 line code to print the logcat in my post. add it to your code. after that, can you tell me which position does the checkbox state = false in the logcatMonotheism
Hello Phan, thanks, I added your line of code. The checkbox state for an item gets set to false as soon as it comes into view. So when I set a checkbox to true by clicking it and then I scroll away from it, the state is still true. But when I scroll back to it, as it and any other items that come into view, it gets set to false right away.Corabella
I was able to implement skadoosh's answer and get it working. Thanks a lot for your help!Corabella
L
0

public class ObservationselectattributeFragment extends Fragment {

DatabaseHandler mDBHandler;
ListView mListView;
SimpleCursorAdapter mSCA;
Cursor mCsr;
ArrayList<String> attributeItems = new ArrayList<>();

public ObservationselectattributeFragment() {
    // Required empty public constructor
}


@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // Inflate the layout for this fragment
    View view1=inflater.inflate(R.layout.fragment_observationselectattribute, container, false);
   //Bundle bundle2 = getArguments();

    Bundle bundle1 = getArguments();
    final int firsttext= bundle1.getInt("TotalCount");
    final String selectedtreatment= bundle1.getString("SelectedTreatment");
   Toast.makeText(getActivity(),"value \n"+firsttext+"\n"+"treatment \n"+selectedtreatment, Toast.LENGTH_SHORT).show();
   // Toast.makeText(getActivity(),"SelectedTreatment \n"+selectedtreatment, Toast.LENGTH_SHORT).show();
    mListView = (ListView)view1.findViewById(R.id.lv001);
    mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
    Button addattribute = (Button)view1.findViewById(R.id.addattribute);
    addattribute.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            String items1="";
            Integer tcount1=0;
            for(String item1:attributeItems){
               items1+="-"+item1+"\n";
                tcount1++;
            }
            Toast.makeText(getActivity(),"you have selected \n"+items1,Toast.LENGTH_LONG).show();
            Toast.makeText(getActivity(),"you have selected \n"+tcount1,Toast.LENGTH_LONG).show();
           /*FragmentTransaction fr= getFragmentManager().beginTransaction();
            fr.replace(R.id.main_container, new ShowObservationDataRecordingFragment()).addToBackStack("ObservationselectattributeFragment");
            fr.commit();*/
            Bundle bundle = new Bundle();
            bundle.putInt("TotalCount2",firsttext);
            bundle.putInt("TotalCount1", tcount1);
            bundle.putString("SelectedTreatment", selectedtreatment);
            Fragment showobservationdatarecordingfragment = new ShowObservationDataRecordingFragment();
            showobservationdatarecordingfragment.setArguments(bundle);
            FragmentManager fragmentManager = getFragmentManager();

            fragmentManager.beginTransaction().replace(R.id.main_container, showobservationdatarecordingfragment).addToBackStack("ObservationselectattributeFragment").commit();
        }

    });
    mDBHandler = new DatabaseHandler(this.getActivity());
    mCsr = mDBHandler.getAllRecords();
    // Prepare a list of the columns to get the data from, for the ListViewt
    String[] columns_to_get_data_from = new String[]{
            DatabaseHandler.KEY_IDS,
            DatabaseHandler.KEY_NAMES,
            DatabaseHandler.KEY_FNAME,
            DatabaseHandler.KEY_MONAME,
            DatabaseHandler.KEY_SNAME
    };

    // Prepare a list of the Views into which to place the data
    int[] itemviews_to_place_data_in = new int[]{
            R.id.euserid,
            R.id.eusername,
            R.id.efname,
            R.id.emoname,
            R.id.esname
    };

    // get and instance of SimpleCursorAdapter
    mSCA = new SimpleCursorAdapter(getActivity(),
            R.layout.listviewitem_record,
            mCsr,
            columns_to_get_data_from,
            itemviews_to_place_data_in,
            0);
    // Save the ListView state (= includes scroll position) as a Parceble
    Parcelable state = mListView.onSaveInstanceState();
    // get and instance of SimpleCursorAdapter the listviewitem_record layout
    mListView.setAdapter(mSCA);
    // Restore previous state (including selected item index and scroll position)
    mListView.onRestoreInstanceState(state);
    mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            String attributeItem1 = ((TextView)view.findViewById(R.id.euserid)).getText().toString();
            String attributeItem2 = ((TextView)view.findViewById(R.id.eusername)).getText().toString();
            String attributeItem3 = ((TextView)view.findViewById(R.id.efname)).getText().toString();
            String attributeItem4 = ((TextView)view.findViewById(R.id.emoname)).getText().toString();
            String attributeItem5 = ((TextView)view.findViewById(R.id.esname)).getText().toString();
            String attributeItem = attributeItem1 + attributeItem2 + attributeItem3 + attributeItem4 + attributeItem5;
           // CheckedTextView box = (CheckedTextView) view.findViewById(R.id.record_checkbox);
         //  box.setChecked(true);
            CheckedTextView checkedTextView = (CheckedTextView) view.findViewById(R.id.record_checkbox);
            if(checkedTextView.isChecked()) {
                checkedTextView.setChecked(false);
            } else {
                checkedTextView.setChecked(true);
            }
            if(attributeItems.contains(attributeItem)){
                attributeItems.remove(attributeItem);//uncheck item

            }
            else
            {
                attributeItems.add(attributeItem);
            }

            Toast.makeText(getActivity(), "Item1 = " + attributeItem1 +"\n"+ "Item2 ="+attributeItem2 +"\n"+"Item3 ="+attributeItem3+"\n"+"Item4 ="+attributeItem4+"\n"+"Item5 ="+attributeItem5, Toast.LENGTH_SHORT).show();


        }
    });
    ((HomeActivity) getActivity())
            .setActionBarTitle("Select Attribute");
    return view1;
}

}

Literalism answered 1/1, 2019 at 7:57 Comment(1)
While this code snippet may be the solution, including an explanation really helps to improve the quality of your post. Remember that you are answering the question for readers in the future, and those people might not know the reasons for your code suggestion.Spotty

© 2022 - 2024 — McMap. All rights reserved.