0

I am trying to make a simple shopping list kind of app, where each item is displayed in a RecyclerView with a CheckBox and two ImageButtons. When the CheckBox is checked, I want to Display the item texts as strike-through.

Here is my RecyclerView Adapter layout file:

<androidx.cardview.widget.CardView
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:id="@+id/adapterCardItemDisplay"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="10dp">

        <TextView
            android:id="@+id/adapterTvItemName"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/adapterTvItemNameBeg"
            android:textSize="18sp"
            android:layout_margin="1dp"
            />

        <TextView
            android:id="@+id/adapterTvItemQuantity"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/adapterTvItemQuantityBeg"
            android:textSize="18sp"
            android:layout_margin="1dp"
            />

        <TextView
            android:id="@+id/adapterTvItemColor"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/adapterTvItemColorBeg"
            android:textSize="18sp"
            android:layout_margin="1dp"
            />

        <TextView
            android:id="@+id/adapterTvItemSize"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/adapterTvItemSizeBeg"
            android:textSize="18sp"
            android:layout_margin="1dp"
            />

        <TextView
            android:id="@+id/adapterTvItemRemarks"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/adapterTvItemRemarksBeg"
            android:textSize="18sp"
            android:layout_margin="1dp"
            />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:layout_marginTop="15dp"

            >

            <CheckBox
                android:id="@+id/adapterChkItemChecked"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"

                />

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"

                android:orientation="horizontal"
                android:gravity="start"
                >

                <ImageButton
                    android:id="@+id/adapterImgBtnEdit"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:background="#FFFCFC"
                    android:src="@android:drawable/ic_menu_edit"
                    android:layout_marginEnd="10dp"/>

                <ImageButton
                    android:id="@+id/adapterImgBtnDelete"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:background="#F8F4F4"
                    android:src="@android:drawable/ic_delete" />
            </LinearLayout>

        </LinearLayout>

    </LinearLayout>
</androidx.cardview.widget.CardView>

And here is my Adapter Java File :

public class ItemDisplayAdapter extends RecyclerView.Adapter <ItemDisplayAdapter.ViewHolder> {
    private static final String TAG = "ItemDisplayAdapter";
    private CardView adapterCardItemDisplay;
    private TextView adapterTvItemName;
    private TextView adapterTvItemQuantity;
    private TextView adapterTvItemColor;
    private TextView adapterTvItemSize;
    private TextView adapterTvItemRemarks;
    private CheckBox adapterChkItemChecked;
    private ImageButton adapterImgBtnEdit;
    private ImageButton adapterImgBtnDelete;

    private Context context;
    private ArrayList<Item> adapterItemArrayList;

    public ItemDisplayAdapter(Context context, ArrayList<Item> adapterItemArrayList) {
        this.context = context;
        this.adapterItemArrayList = adapterItemArrayList;
    }

    private void checkAnItem (boolean isChecked,int position) {
        if (isChecked) {
            Log.d(TAG, "checkAnItem: here");
            adapterTvItemName.setPaintFlags(adapterTvItemName.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
            adapterTvItemQuantity.setPaintFlags(adapterTvItemQuantity.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
            adapterTvItemColor.setPaintFlags(adapterTvItemColor.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
            adapterTvItemSize.setPaintFlags(adapterTvItemSize.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
            adapterTvItemRemarks.setPaintFlags(adapterTvItemRemarks.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
        }else {
            adapterTvItemName.setPaintFlags(adapterTvItemName.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG));
            adapterTvItemQuantity.setPaintFlags(adapterTvItemQuantity.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG));
            adapterTvItemColor.setPaintFlags(adapterTvItemColor.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG));
            adapterTvItemSize.setPaintFlags(adapterTvItemSize.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG));
            adapterTvItemRemarks.setPaintFlags(adapterTvItemRemarks.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG));
        }
        //invalidateViews();

    }

    private void invalidateViews () {
        adapterTvItemName.invalidate();
        adapterTvItemQuantity.invalidate();
        adapterTvItemColor.invalidate();
        adapterTvItemSize.invalidate();
        adapterTvItemRemarks.invalidate();
    }

    private void editAnItem () {

    }

    private void deleteAnItem () {

    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.adapter_item_list_view,parent,false);
        ViewHolder viewHolder = new ViewHolder(view);
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, final int position) {
        //Log.d(TAG, "onBindViewHolder: "+position);
        Item item = adapterItemArrayList.get(position);
        adapterCardItemDisplay.setTag(item.getId());
        String nameString = context.getResources().getString(R.string.adapterTvItemNameBeg)+
                " "+item.getName();
        adapterTvItemName.setText(nameString);
        String quantityString = context.getResources().getString(R.string.adapterTvItemQuantityBeg)+
                " "+item.getQuantity();
        adapterTvItemQuantity.setText(quantityString);
        if (item.getColor().isEmpty()) {
            adapterTvItemColor.setVisibility(View.GONE);
        } else {
            adapterTvItemColor.setVisibility(View.VISIBLE);
            String colorString = context.getResources().getString(R.string.adapterTvItemColorBeg) +
                    " " + item.getColor();
            adapterTvItemColor.setText(colorString);
        }
        if (item.getSize() == 0) {
            adapterTvItemSize.setVisibility(View.GONE);
        } else {
            adapterTvItemSize.setVisibility(View.VISIBLE);
            String sizeString = context.getResources().getString(R.string.adapterTvItemSizeBeg)+
                    " "+item.getSize();
            adapterTvItemSize.setText(sizeString);
        }
        if (item.getRemarks().isEmpty()) {
            adapterTvItemRemarks.setVisibility(View.GONE);
        } else {
            adapterTvItemRemarks.setVisibility(View.VISIBLE);
            String remarksString = context.getResources().getString(R.string.adapterTvItemRemarksBeg)+
                    " "+item.getRemarks();
            adapterTvItemRemarks.setText(remarksString);
        }
        adapterChkItemChecked.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                Log.d(TAG, "onCheckedChanged: here");
                checkAnItem(isChecked,position);
                notifyItemChanged(position);
                adapterChkItemChecked.setChecked(isChecked);

            }
        });
        adapterImgBtnEdit.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                editAnItem();
            }
        });
        adapterImgBtnDelete.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                deleteAnItem();
            }
        });
    }

    @Override
    public int getItemCount() {
        return adapterItemArrayList.size();
    }

    public class ViewHolder extends RecyclerView.ViewHolder {

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            adapterCardItemDisplay = itemView.findViewById(R.id.adapterCardItemDisplay);
            adapterTvItemName = itemView.findViewById(R.id.adapterTvItemName);
            adapterTvItemQuantity = itemView.findViewById(R.id.adapterTvItemQuantity);
            adapterTvItemColor = itemView.findViewById(R.id.adapterTvItemColor);
            adapterTvItemSize = itemView.findViewById(R.id.adapterTvItemSize);
            adapterTvItemRemarks = itemView.findViewById(R.id.adapterTvItemRemarks);
            adapterChkItemChecked = itemView.findViewById(R.id.adapterChkItemChecked);
            adapterImgBtnEdit = itemView.findViewById(R.id.adapterImgBtnEdit);
            adapterImgBtnDelete = itemView.findViewById(R.id.adapterImgBtnDelete);

        }
    }
}

I am unable to get the expected behavior. Clicking on a CheckBox, doesn't give the strike-through effect on that item, maybe clicking again makes change to another item, (say clicking checkbox on item 0 actually makes the strike-through change on item 4 as such), sometimes i get the strike-through effect on repeated clicks but at that time the checkbox doesn't stay checked. So I am at a loss how to achieve it.

Invalidating the views didn't work, neither did the notifyItemChanged(position) method.

Any Suggestions?

UPDATE: so i followed Daniel's suggestion, and it got rid of 50% of the Problem, but the checkbox keeps getting unchecked after the view update, i suppose that's an expected behavior since the view is getting updated, that's why i had the code in the 'CheckedChangeListener' that sets the checkBox with the 'isChecked' parameter after 'notifyItemChanged(position)' but it doesn't seem to work properly. What am I doing wrong?

Updated Code for the adapter class:

public class ItemDisplayAdapter extends RecyclerView.Adapter <ItemDisplayAdapter.ViewHolder> {
    private static final String TAG = "ItemDisplayAdapter";


    private Context context;
    private ArrayList<Item> adapterItemArrayList;

    public ItemDisplayAdapter(Context context, ArrayList<Item> adapterItemArrayList) {
        this.context = context;
        this.adapterItemArrayList = adapterItemArrayList;
    }

    private void checkAnItem (ViewHolder holder,boolean isChecked,int position) {
        if (isChecked) {
            Log.d(TAG, "checkAnItem: here");
            holder.adapterTvItemName.setPaintFlags(holder.adapterTvItemName.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
            holder.adapterTvItemQuantity.setPaintFlags(holder.adapterTvItemQuantity.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
            holder.adapterTvItemColor.setPaintFlags(holder.adapterTvItemColor.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
            holder.adapterTvItemSize.setPaintFlags(holder.adapterTvItemSize.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
            holder.adapterTvItemRemarks.setPaintFlags(holder.adapterTvItemRemarks.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
        }else {
            holder.adapterTvItemName.setPaintFlags(holder.adapterTvItemName.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG));
            holder.adapterTvItemQuantity.setPaintFlags(holder.adapterTvItemQuantity.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG));
            holder.adapterTvItemColor.setPaintFlags(holder.adapterTvItemColor.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG));
            holder.adapterTvItemSize.setPaintFlags(holder.adapterTvItemSize.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG));
            holder.adapterTvItemRemarks.setPaintFlags(holder.adapterTvItemRemarks.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG));
        }
        //invalidateViews();

    }

    private void invalidateViews (ViewHolder holder) {
        holder.adapterTvItemName.invalidate();
        holder.adapterTvItemQuantity.invalidate();
        holder.adapterTvItemColor.invalidate();
        holder.adapterTvItemSize.invalidate();
        holder.adapterTvItemRemarks.invalidate();
    }

    private void editAnItem () {

    }

    private void deleteAnItem () {

    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.adapter_item_list_view,parent,false);
        ViewHolder viewHolder = new ViewHolder(view);
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(@NonNull final ViewHolder holder, final int position) {
        //Log.d(TAG, "onBindViewHolder: "+position);
        Item item = adapterItemArrayList.get(position);
        holder.adapterCardItemDisplay.setTag(item.getId());
        String nameString = context.getResources().getString(R.string.adapterTvItemNameBeg)+
                " "+item.getName();
        holder.adapterTvItemName.setText(nameString);
        String quantityString = context.getResources().getString(R.string.adapterTvItemQuantityBeg)+
                " "+item.getQuantity();
        holder.adapterTvItemQuantity.setText(quantityString);
        if (item.getColor().isEmpty()) {
            holder.adapterTvItemColor.setVisibility(View.GONE);
        } else {
            holder.adapterTvItemColor.setVisibility(View.VISIBLE);
            String colorString = context.getResources().getString(R.string.adapterTvItemColorBeg) +
                    " " + item.getColor();
            holder.adapterTvItemColor.setText(colorString);
        }
        if (item.getSize() == 0) {
            holder.adapterTvItemSize.setVisibility(View.GONE);
        } else {
            holder.adapterTvItemSize.setVisibility(View.VISIBLE);
            String sizeString = context.getResources().getString(R.string.adapterTvItemSizeBeg)+
                    " "+item.getSize();
            holder.adapterTvItemSize.setText(sizeString);
        }
        if (item.getRemarks().isEmpty()) {
            holder.adapterTvItemRemarks.setVisibility(View.GONE);
        } else {
            holder.adapterTvItemRemarks.setVisibility(View.VISIBLE);
            String remarksString = context.getResources().getString(R.string.adapterTvItemRemarksBeg)+
                    " "+item.getRemarks();
            holder.adapterTvItemRemarks.setText(remarksString);
        }
        holder.adapterChkItemChecked.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                Log.d(TAG, "onCheckedChanged: here");
                checkAnItem(holder,isChecked,position);
                notifyItemChanged(position);
               holder.adapterChkItemChecked.setChecked(isChecked);

            }
        });
        holder.adapterImgBtnEdit.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                editAnItem();
            }
        });
        holder.adapterImgBtnDelete.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                deleteAnItem();
            }
        });
    }

    @Override
    public int getItemCount() {
        return adapterItemArrayList.size();
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {

        private CardView adapterCardItemDisplay;
        private TextView adapterTvItemName;
        private TextView adapterTvItemQuantity;
        private TextView adapterTvItemColor;
        private TextView adapterTvItemSize;
        private TextView adapterTvItemRemarks;
        private CheckBox adapterChkItemChecked;
        private ImageButton adapterImgBtnEdit;
        private ImageButton adapterImgBtnDelete;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            adapterCardItemDisplay = itemView.findViewById(R.id.adapterCardItemDisplay);
            adapterTvItemName = itemView.findViewById(R.id.adapterTvItemName);
            adapterTvItemQuantity = itemView.findViewById(R.id.adapterTvItemQuantity);
            adapterTvItemColor = itemView.findViewById(R.id.adapterTvItemColor);
            adapterTvItemSize = itemView.findViewById(R.id.adapterTvItemSize);
            adapterTvItemRemarks = itemView.findViewById(R.id.adapterTvItemRemarks);
            adapterChkItemChecked = itemView.findViewById(R.id.adapterChkItemChecked);
            adapterImgBtnEdit = itemView.findViewById(R.id.adapterImgBtnEdit);
            adapterImgBtnDelete = itemView.findViewById(R.id.adapterImgBtnDelete);

        }
    }


}

Here's a GIF depicting the Problem :

GIF depicting the workings of the view

I hope you can see how the checkBox keeps resetting itself.

Thanks, and best regards.

Arun123
  • 118
  • 6
  • Can you post your ```onItemClick``` function as well? – Amin Apr 02 '20 at 18:14
  • sorry which method are you talking about? I didn't use any onItemClick()? – Arun123 Apr 02 '20 at 18:26
  • Take a look at [this example](https://stackoverflow.com/questions/40584424/simple-android-recyclerview-example), I followed it heavily for most of my RecyclerView adapters. The ```onItemClick``` function goes in the activity using the adapter, and in that function implementation you can change the style of the textview. Let me know if you need help with specifics – Amin Apr 02 '20 at 18:32

2 Answers2

0

There might be more than one issue here, but let's start with one first.

Your view references are in the adapter class.

    private CardView adapterCardItemDisplay;
    private TextView adapterTvItemName;
    private TextView adapterTvItemQuantity;
    private TextView adapterTvItemColor;
    private TextView adapterTvItemSize;
    private TextView adapterTvItemRemarks;
    private CheckBox adapterChkItemChecked;
    private ImageButton adapterImgBtnEdit;
    private ImageButton adapterImgBtnDelete;

It should be in the view holder class.

The rationale is that each view has its own set of view references.

Now to correct this, make the inner class static and move the above references inside the view holder class.

    static class ViewHolder extends RecyclerView.ViewHolder {
        private CardView adapterCardItemDisplay;
        private TextView adapterTvItemName;
        private TextView adapterTvItemQuantity;
        private TextView adapterTvItemColor;
        private TextView adapterTvItemSize;
        private TextView adapterTvItemRemarks;
        private CheckBox adapterChkItemChecked;
        private ImageButton adapterImgBtnEdit;
        private ImageButton adapterImgBtnDelete;

        ViewHolder(@NonNull View itemView) {
            ...
        }
    }

Adjust the usage in onBindViewHolder by appending holder. in front.

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, final int position) {
        ...
        holder.adapterTvItemName.setText(nameString);
        holder.adapterTvItemQuantity.setText(quantityString);
                holder.adapterChkItemChecked.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                Log.d(TAG, "onCheckedChanged: here");
                checkAnItem(holder, isChecked,position);
                notifyItemChanged(position);
                holder.adapterChkItemChecked.setChecked(isChecked);

            }
        });
        holder.adapterImgBtnEdit.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                editAnItem();
            }
        });
        ... etc...
    }

Add the view holder as a new parameter to checkAnItem and use the view holder to access the enclosed view references. Same idea as in onBindViewHolder.

    private void checkAnItem (ViewHolder holder, boolean isChecked,int position) {  
        ...
      holder.adapterTvItemSize.setPaintFlags(holder.adapterTvItemSize.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
        ...
    }

That's it for now. Let me know of the progress.

Edit:

The problem with the checkbox unchecking randomly is a well known one. See this answer. Basically when it is checked, you need to toggle a Boolean in your adapter to keep track of which item is checked.

Daniel
  • 400
  • 1
  • 2
  • 12
  • Hello thank you for your kind help, yes it solved 50% of the problem I was having, I have updated my question with the progress made. Thanks you. Also can you please tell me why you suggested making the inner class static? Any Specific reason? – Arun123 Apr 04 '20 at 14:05
0

You could add one boolean flag checked in the Item class,

// CREATE

class Item {
  private boolean checked;

  public boolean isChecked() {
    return checked;
  }

  public void setChecked(boolean checked) {
    this.checked = checked;
  }  
}

Display the item texts as strike-through in onBindViewHolder

// GET

@Override
public void onBindViewHolder(@NonNull final ViewHolder holder, final int position) {
    Item item = adapterItemArrayList.get(position);
    if (item.isChecked()) {
        // checked style
    } else {
       // unchecked style
    }
}

you better setOnClickListener in ViewHolder rather than onBindViewHolder

// SET

public ViewHolder(@NonNull View itemView) {
    super(itemView);
    adapterChkItemChecked = itemView.findViewById(R.id.adapterChkItemChecked);
    adapterChkItemChecked.setOnClickListener(v -> {
        int position = getLayoutPosition();
        Item item = adapterItemArrayList.get(position);
        item.setChecked(adapterChkItemChecked.isChecked())
});
}
Jared DC
  • 35
  • 6