使用 CursorAdapter 维护列表视图中的复选框状态

Maintaining checkbox states in listview with CursorAdapter

对于我的 Android 项目,我有一个列表视图,其中每个项目都有一个复选框。使用 CursorAdapter class 从 SQLite 数据库加载数据。但是,每当我滚动时,复选框的位置都会移动并向下移动到列表视图的下一部分。我该如何解决这个问题? GIF of my CheckBox Problem 这是我的光标适配器 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);
    }
}

这是我的列表项 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>

这是我的 xml,其中包含一个列表视图

<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>

我在 Whosebug 上查看了很多不同的解决方案,但我无法在自己的应用程序中成功实现。例如,这个 post 有类似的问题,但它的解决方案使用了 getView ,我很难理解如何使用 newView 和 bindView 来实现它。

其他一些解决方案可能是不涉及游标适配器的示例。非常感谢任何帮助,非常感谢! 编辑 #1:合并 Phan 的更改后,当我滚动列表视图时,复选框状态重置为 false 而不是保持其状态(参见 )。

试试这个

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));
    }
}

原因: ListView 重新使用视图。

解决方案:

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);
    }
}

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;
}

}