简体   繁体   中英

RecyclerView with checkbox checked/unchecked automatically when I am scrolling

this is my QuestionHolder :

 private class QuestionHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

    private JSONObject question;
    private CheckBox checkBox;
    private TextView question_txt;
    public QuestionHolder(final LayoutInflater inflater, ViewGroup parent) {
        super(inflater.inflate(R.layout.question_item, parent, false));

        checkBox  = (CheckBox) itemView.findViewById(R.id.question_checkbox);

        checkBox.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //try {
                    //Toast.makeText(getActivity(), "ID: " + question.get("_id"), Toast.LENGTH_SHORT).show();
                //} catch (JSONException e) { }
                // TODO: If this was checked then add the question's id to the answered list
            }
        });
        question_txt = (TextView) itemView.findViewById(R.id.question);
        itemView.setOnClickListener(this);
    }

here I am binding the questiontxt :

public void bind(JSONObject question) {
  this.question = question;
  try {
    question_txt.setText(question.getString("_question"));
  } catch (JSONException je) { }
}

@Override
public void onClick(View v) {
  // TODO: If this was checked then add the question's id to the answered list
}
}

this is my Adapter which extend QuestionHolder :

private class QuestionAdapter extends RecyclerView.Adapter<QuestionHolder> {

    private JSONArray questions;

    public QuestionAdapter(JSONArray questions) {
        this.questions = questions;
    }

    @Override
    public QuestionHolder onCreateViewHolder(ViewGroup parent, int viewType) 
{
        return new QuestionHolder(LayoutInflater.from(getActivity()), parent);
    }

this is onBindViewHolder :

@Override
public void onBindViewHolder(QuestionHolder holder, int position) {
  try {
    final JSONObject question = questions.getJSONObject(position);
    holder.bind(question);
  } catch (JSONException je) { }
}

@Override
public int getItemCount() {
  return questions.length();
}
}

this is QuestionHolder :

private class QuestionHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

    private JSONObject question;
    private CheckBox checkBox;
    private TextView question_txt;
    public QuestionHolder(final LayoutInflater inflater, ViewGroup parent) {
        super(inflater.inflate(R.layout.question_item, parent, false));

        checkBox  = (CheckBox) itemView.findViewById(R.id.question_checkbox);

        checkBox.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //try {
                    //Toast.makeText(getActivity(), "ID: " + question.get("_id"), Toast.LENGTH_SHORT).show();
                //} catch (JSONException e) { }
                // TODO: If this was checked then add the question's id to the answered list
            }
        });
        question_txt = (TextView) itemView.findViewById(R.id.question);
        itemView.setOnClickListener(this);
    }

    public void bind(JSONObject question) {
        this.question = question;
        try {
            question_txt.setText(question.getString("_question"));
        } catch (JSONException je) { }
    }

    @Override
    public void onClick(View v) {
        // TODO: If this was checked then add the question's id to the answered list
    }
}

This is because your ViewHolder is recycled when scrolling. So, you need to keep the state of the CheckBox for each item position. You can use SparseBooleanArray to handle this.

You can do something like this:

private class QuestionAdapter extends RecyclerView.Adapter<QuestionHolder> {

  SparseBooleanArray checkedItems = new SparseBooleanArray();

  ...

  @Override
  public void onBindViewHolder(QuestionHolder holder, int position) {
    try {
      final JSONObject question = questions.getJSONObject(position);

      // Remember to change access modifier of checkBox in your ViewHolder
      // Get the state from checkedItems. If no previous value, it will return false.
      holder.checkBox.setChecked(checkedItems.get(position));

      // This is a sample. Do not use create listener here.
      checkBox.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
          // save the check state
          checkedItems.put(position, true);
        }

      holder.bind(question);
    } catch (JSONException je) { }
  }

  ...

}

RecyclerView removes (recycles) the unseen views from the layout on scrolling, this is the basic behavior of recyclerView in order to reduce memory use.

So when a view with a checkbox is "recycled", a checked checkbox gets unchecked and if it has a listener, the listener gets called.

You can remove the listener from the view when it is recycled. Just override the onViewRecycled method.

 @Override
    public void onViewRecycled(@NonNull MyViewHolder holder) {
        if (holder.checkBox != null) {
            holder.checkBox.setOnClickListener(null);
        }
        super.onViewRecycled(holder);
    }

When the view is constructed again, while scrolling, your listener will also be added again.

After some research on this one of the complex issue

I did this like this for my currency convertor project:

Below are steps:

  1. Created A Custom Adapter with inner ViewHolder
  2. Created an Interference for Selection changed and to notify the Adaper in MainActivty
  3. Created an Model Class Created an layout for My list

Coding:

Adapter and View Holder

    class Adapter extends RecyclerView.Adapter<Adapter.VH> {
    ArrayList<Currency> list, checkedCurrencies = new ArrayList<>();
    CurrencySelectionChangelistener listener;


    public Adapter(@NonNull ArrayList<Currency> list, CurrencySelectionChangelistener listener) {
        this.list = list;
        this.listener = listener;
    }
    @NonNull
    @Override
    public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new VH(LayoutInflater.from(getContext()).inflate(R.layout.layout_list_of_all_items, parent, false));
    }
    @Override
    public void onBindViewHolder(@NonNull VH holder, @SuppressLint("RecyclerView") int position) {
        Currency currentCurrency = list.get(position);
        holder.checkBox.setChecked(currentCurrency.isChecked());
        holder.mDis.setText(currentCurrency.getDescription());

        //ignore below line it's just for sorting the string to set On TextView and with another purpose
        String name = currentCurrency.getName().toLowerCase().replace(" ", "");
        holder.mName.setText(name.toUpperCase());
        holder.mLogo.setImageResource(currentCurrency.getLogo(name));

        holder.checkBox.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (holder.checkBox.isChecked()) {
                    list.get(position).setChecked(true);
                    checkedCurrencies.add(currentCurrency);
                } else if (!holder.checkBox.isChecked()) {
                    list.get(position).setChecked(false);
                    checkedCurrencies.remove(currentCurrency);
                }
                //calling the interference's function
                onSelection(checkedCurrencies);
            }
        });


    }

    @Override
    public int getItemCount() {
        return list.size();
    }
    class VH extends RecyclerView.ViewHolder {
        TextView mName, mDis;
        ImageView mLogo;
        CheckBox checkBox;
        public VH(@NonNull View itemView) {
            super(itemView);
            checkBox = itemView.findViewById(R.id.checkboxAllItems);
            mLogo = itemView.findViewById(R.id.ImageViewAllItemLogo);
            mName = itemView.findViewById(R.id.textViewAllItemCName);
            mDis = itemView.findViewById(R.id.textViewAllItemCDis);
        }
    }
}

Interface

public interface CurrencySelectionChangelistener {
public void onSelection(ArrayList<Currency> currencies);}

Fragment in Which I'm using RecyclerView

public class AddMoreCurrencyFragment extends Fragment implements CurrencySelectionChangelistener {

FragmentAddNewCurrencyBinding binding;
ArrayList<Currency> currencies;
Adapter adapter;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    binding = FragmentAddNewCurrencyBinding.inflate(inflater);
    CurrencyContainerActivity.title.setVisibility(View.VISIBLE);
    CurrencyContainerActivity.title.setText("Add Currency");
    CurrencyContainerActivity.backBtn.setVisibility(View.VISIBLE);

    currencies = new ArrayList<>();
    for (Currency c : CurrencyContainerActivity.listOfCurrencies) {
        currencies.add(new Currency(c.getName(), c.getDescription(), false));
    }
    adapter = new Adapter(currencies, this);
    binding.recView.setAdapter(adapter);


    binding.done.setOnClickListener(view -> {

    });

    return binding.getRoot();
}


@Override
public void onSelection(ArrayList<Currency> currencies) {
    CurrencyContainerActivity.listToAdd.addAll(currencies);
    Toast.makeText(getContext(), currencies.toString(), Toast.LENGTH_LONG).show();
    adapter.notifyDataSetChanged();

}

}

xml layout for each item

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="60dp"
    android:background="@color/white"
    android:orientation="vertical"
    android:weightSum="3">

    <LinearLayout

        android:layout_width="match_parent"
        android:layout_height="59dp"
        android:gravity="center_vertical"
        android:orientation="horizontal"
        android:padding="10dp"
        android:weightSum="3">

        <androidx.cardview.widget.CardView

            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="0.89"
            app:cardCornerRadius="16dp">

            <ImageView
                android:id="@+id/ImageViewAllItemLogo"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:src="@drawable/flag" />

        </androidx.cardview.widget.CardView>

        <TextView
            android:id="@+id/textViewAllItemCName"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginEnd="19dp"
            android:layout_weight=".75"
            android:fontFamily="@font/poppins"
            android:text="@string/pkr"
            android:textAlignment="center"
            android:textAllCaps="true"
            android:textColor="@color/black"
            android:textSize="16sp" />
        <TextView
            android:id="@+id/textViewAllItemCDis"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginEnd="10dp"
            android:layout_weight="0.47"
            android:fontFamily="@font/poppins"
            android:text="@string/pkr"
            android:textAlignment="textStart"
            android:textColor="@color/black"
            android:textSize="13sp" />
        <CheckBox
            android:id="@+id/checkboxAllItems"
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:layout_weight="0.90"
            android:background="@drawable/radio_selector"
            android:button="@android:color/transparent" />
    </LinearLayout>
    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="#3F000000" />

</LinearLayout>

Model Class

public class Currency {
    private String Name, Description;
    private int logo;
    boolean isChecked;

    public Currency(String name, String description, boolean isChecked) {
        Name = name;
        Description = description;
        this.isChecked = isChecked;
    }

    public boolean isChecked() {
        return isChecked;
    }

    public void setChecked(boolean checked) {
        isChecked = checked;
    }

    public String getName() {
        return Name;
    }

    public void setName(String name) {
        Name = name;
    }

    public String getDescription() {
        return Description;
    }

    public void setDescription(String description) {
        Description = description;
    }
    
}

if anything i missed can for that. Thanks

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM