根据用户选择对列表视图项进行持久更改

Make persistent changes to listview items based on user selections

所以几年后我又回到了 android 编程,我正在为一些我认为非常简单的事情而苦苦挣扎。我正在使用具有自定义布局的自定义数组适配器填充列表视图。 基本上,数组列表的每个项目都是一个自定义 class(命名卡),具有 4 个属性:(int)id、(String)name、(boolean)include、(int)deck(想法是用户选择要包含在 3 副不同牌组中的每副牌)。 我创建了一个简单的布局,其中包含 2 个文本视图、1 个复选框和 1 个单选组,里面有 3 个单选按钮(和一堆图标)(见下文)。 我的问题是,尽管列表视图被填充并且显示正确,但每当我(用户)更改某些内容(例如点击复选框或单选按钮)并滚动时,一切都搞砸了(我所做的选择被复制到其他行)。我尝试在适配器的 getView() 方法中设置侦听器并通知数据更改,但无济于事。我知道视图会被回收,但显然我不明白如何回收。任何帮助将不胜感激。

card_layout.xml

<ImageView
    android:id="@+id/icon_eye"
    android:layout_width="20dp"
    android:layout_height="20dp"
    android:layout_gravity="center_vertical"
    app:srcCompat="@drawable/eye" />

<TextView
    android:id="@+id/tv_card_id"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="5dp"
    android:layout_marginLeft="5dp"
    android:layout_weight="1"
    android:visibility="gone"
    android:text="" />

<TextView
    android:id="@+id/tv_card_name"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="5dp"
    android:layout_marginLeft="5dp"
    android:layout_weight="2"
    android:text="TextView" />

<CheckBox
    android:id="@+id/cb_include"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginLeft="30dp"
    android:layout_marginRight="10dp"
    android:checked="true"
    android:text="" />


<RadioGroup
    android:id="@+id/rbg_decks"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_weight="1"
    android:orientation="horizontal">

    <ImageView
        android:id="@+id/icon_deck_1"
        android:layout_width="20dp"
        android:layout_height="20dp"
        android:layout_gravity="center_vertical"
        android:clickable="false"
        android:focusable="false"
        app:srcCompat="@drawable/die_1" />

    <RadioButton
        android:id="@+id/rb_deck_1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="30dp"
        android:layout_marginRight="30dp"
        android:text="" />

    <ImageView
        android:id="@+id/icon_deck_2"
        android:layout_width="20dp"
        android:layout_height="20dp"
        android:layout_gravity="center_vertical"
        android:clickable="false"
        android:focusable="false"
        app:srcCompat="@drawable/die_2" />

    <RadioButton
        android:id="@+id/rb_deck_2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="30dp"
        android:layout_marginRight="30dp"
        android:text="" />

    <ImageView
        android:id="@+id/icon_deck_3"
        android:layout_width="20dp"
        android:layout_height="20dp"
        android:layout_gravity="center_vertical"
        android:clickable="false"
        android:focusable="false"
        app:srcCompat="@drawable/die_3" />

    <RadioButton
        android:id="@+id/rb_deck_3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="" />

</RadioGroup>

视觉示例:

这是自定义适配器

AdapterCard.xml

public class AdapterCard extends ArrayAdapter<Card> {

Activity context;
AdapterCard(Activity context, ArrayList<Card> list_cards)
{
    super(context, R.layout.card_layout, list_cards);
    this.context = context;
}

static class ViewHolder {
    private ImageView icon_eye,icon_deck_1,icon_deck_2,icon_deck_3;
    private TextView tv_card_name, tv_card_id;
    private CheckBox cb_include;
    private RadioGroup rbg_decks;
    private RadioButton rb_1,rb_2,rb_3;
}

public View getView(int position, View convertView, ViewGroup parent)
{

    ViewHolder mViewHolder = null;

    if(convertView == null){
        mViewHolder = new ViewHolder();
        LayoutInflater vi = context.getLayoutInflater();
        convertView = vi.inflate(R.layout.card_layout, parent, false);
        mViewHolder.icon_eye = (ImageView) convertView.findViewById(R.id.icon_eye);
        mViewHolder.icon_deck_1 = (ImageView) convertView.findViewById(R.id.icon_deck_1);
        mViewHolder.icon_deck_2 = (ImageView) convertView.findViewById(R.id.icon_deck_2);
        mViewHolder.icon_deck_3 = (ImageView) convertView.findViewById(R.id.icon_deck_3);
        mViewHolder.tv_card_name = (TextView) convertView.findViewById(R.id.tv_card_name);
        mViewHolder.tv_card_id = (TextView) convertView.findViewById(R.id.tv_card_id);
        mViewHolder.cb_include = (CheckBox) convertView.findViewById(R.id.cb_include);
        mViewHolder.rbg_decks = (RadioGroup) convertView.findViewById(R.id.rbg_decks);
        mViewHolder.rb_1 = (RadioButton) convertView.findViewById(R.id.rb_deck_1);
        mViewHolder.rb_2 = (RadioButton) convertView.findViewById(R.id.rb_deck_2);
        mViewHolder.rb_3 = (RadioButton) convertView.findViewById(R.id.rb_deck_3);
        convertView.setTag(mViewHolder);
    } else{
        mViewHolder =   (ViewHolder) convertView.getTag();
    }

    Card card_i = getItem(position);

    mViewHolder.tv_card_name.setText(card_i.name);
    mViewHolder.tv_card_id.setText(card_i.getStringId());

    // I would like to do this only ONCE to initialize the radiobuttons 
    //with the card default deck data but as it is now, 
    //every time I scroll, the radiobuttons are reset
    switch(card_i.deck) {
        case 1:
            mViewHolder.rb_1.setChecked(true);
            break;
        case 2:
            mViewHolder.rb_2.setChecked(true);
            break;
        case 3:
            mViewHolder.rb_3.setChecked(true);
            break;
        default:
            break;
    }


    
    mViewHolder.cb_include.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            card_i.include = isChecked;
            AdapterCard.this.notifyDataSetChanged();
        }
    });

    /*mViewHolder.rbg_decks.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(RadioGroup group, int checkedId) {
            card_i.deck = checkedId;
            AdapterCard.this.notifyDataSetChanged();
        }
    });*/

    return convertView;
}

}

这是 MainActivity 中我设置适配器的部分:

    ArrayList<Card> list = ... //populate the list
    listView = findViewById(R.id.lv_cards);
    AdapterCard adapter = new AdapterCard(this,list);
    listView.setAdapter(adapter);

编辑: 我忘了补充一点,最终目标是通过遍历整个列表来恢复所有用户选择。至此我已经成功获取了Card对象,但是由于滚动时位置发生变化,所以没有用:

for(int i = 0; i < adapter.getCount(); i++){
    Card card_i = adapter.getItem(i);
    // retrieve card data to generate the decks
}

我最终设法通过简单地将我感兴趣的状态存储在布尔列表中并监听“点击”而不是更改状态来做到这一点。适配器现在看起来:

public class AdapterCard extends ArrayAdapter<Card> {


Activity context;
public List<Boolean> cb_include_state, cb_must_include_state, rb_deck_1_state, rb_deck_2_state, rb_deck_3_state;

AdapterCard(Activity context, ArrayList<Card> list_cards)
{
    super(context, R.layout.card_layout, list_cards);
    this.context = context;
    // Initialize states
    cb_include_state = new ArrayList<Boolean>(Collections.nCopies(list_cards.size(), true));
    cb_must_include_state = new ArrayList<Boolean>(Collections.nCopies(list_cards.size(), false));
    rb_deck_1_state = new ArrayList<Boolean>(Collections.nCopies(list_cards.size(), false));
    rb_deck_2_state = new ArrayList<Boolean>(Collections.nCopies(list_cards.size(), false));
    rb_deck_3_state = new ArrayList<Boolean>(Collections.nCopies(list_cards.size(), false));
    for (int i=0; i<list_cards.size(); i++) {
        Card card_i = list_cards.get(i);
        switch(card_i.deck) {
            case 1:
                rb_deck_1_state.set(i,true);
                break;
            case 2:
                rb_deck_2_state.set(i,true);
                break;
            case 3:
                rb_deck_3_state.set(i,true);
                break;
            default:
                break;
        }
    }
}

static class ViewHolder {
    private ImageView icon_eye,icon_deck_1,icon_deck_2,icon_deck_3;
    private TextView tv_card_name, tv_card_id;
    private CheckBox cb_include, cb_must_include;
    private RadioGroup rbg_decks;
    private RadioButton rb_1,rb_2,rb_3;
}

public View getView(int position, View convertView, ViewGroup parent)
{

    ViewHolder mViewHolder = null;

    if(convertView == null){
        mViewHolder = new ViewHolder();
        LayoutInflater vi = context.getLayoutInflater();
        convertView = vi.inflate(R.layout.card_layout, parent, false);
        mViewHolder.icon_eye = (ImageView) convertView.findViewById(R.id.icon_eye);
        mViewHolder.icon_deck_1 = (ImageView) convertView.findViewById(R.id.icon_deck_1);
        mViewHolder.icon_deck_2 = (ImageView) convertView.findViewById(R.id.icon_deck_2);
        mViewHolder.icon_deck_3 = (ImageView) convertView.findViewById(R.id.icon_deck_3);
        mViewHolder.tv_card_name = (TextView) convertView.findViewById(R.id.tv_card_name);
        mViewHolder.tv_card_id = (TextView) convertView.findViewById(R.id.tv_card_id);
        mViewHolder.cb_include = (CheckBox) convertView.findViewById(R.id.cb_include);
        mViewHolder.cb_must_include = (CheckBox) convertView.findViewById(R.id.cb_must_include);
        mViewHolder.rbg_decks = (RadioGroup) convertView.findViewById(R.id.rbg_decks);
        mViewHolder.rb_1 = (RadioButton) convertView.findViewById(R.id.rb_deck_1);
        mViewHolder.rb_2 = (RadioButton) convertView.findViewById(R.id.rb_deck_2);
        mViewHolder.rb_3 = (RadioButton) convertView.findViewById(R.id.rb_deck_3);
        convertView.setTag(mViewHolder);
    } else{
        mViewHolder =   (ViewHolder) convertView.getTag();
    }

    Card card_i = getItem(position);
    mViewHolder.tv_card_name.setText(card_i.name);
    mViewHolder.tv_card_id.setText(card_i.getStringId());
    mViewHolder.cb_include.setChecked(cb_include_state.get(position));
    mViewHolder.cb_must_include.setChecked(cb_must_include_state.get(position));
    mViewHolder.rb_1.setChecked(rb_deck_1_state.get(position));
    mViewHolder.rb_2.setChecked(rb_deck_2_state.get(position));
    mViewHolder.rb_3.setChecked(rb_deck_3_state.get(position));

    mViewHolder.cb_must_include.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            CheckBox cb = (CheckBox) v;
            String checked = cb.isChecked() ? "yes" : "no";
            cb_must_include_state.set(position,cb.isChecked());
            Log.e("onclick must include","item i: " + position + " checked: " + checked);
        }
    });

    mViewHolder.cb_include.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            CheckBox cb = (CheckBox) v;
            String checked = cb.isChecked() ? "yes" : "no";
            cb_include_state.set(position,cb.isChecked());
            Log.e("onclick include","item i: " + position + " checked: " + checked);
        }
    });

    mViewHolder.rb_1.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            RadioButton rb = (RadioButton) v;
            String checked = rb.isChecked() ? "yes" : "no";
            rb_deck_1_state.set(position,rb.isChecked());
            rb_deck_2_state.set(position,!rb.isChecked());
            rb_deck_3_state.set(position,!rb.isChecked());
            Log.e("onclick rb1","item i: " + position + " checked: " + checked);
        }
    });

    mViewHolder.rb_2.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            RadioButton rb = (RadioButton) v;
            String checked = rb.isChecked() ? "yes" : "no";
            rb_deck_1_state.set(position,!rb.isChecked());
            rb_deck_2_state.set(position,rb.isChecked());
            rb_deck_3_state.set(position,!rb.isChecked());
            Log.e("onclick rb2","item i: " + position + " checked: " + checked);
        }
    });

    mViewHolder.rb_3.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            RadioButton rb = (RadioButton) v;
            String checked = rb.isChecked() ? "yes" : "no";
            rb_deck_1_state.set(position,!rb.isChecked());
            rb_deck_2_state.set(position,!rb.isChecked());
            rb_deck_3_state.set(position,rb.isChecked());
            Log.e("onclick rb3","item i: " + position + " checked: " + checked);
        }
    });

    return convertView;
}
}