4

I have a problem with recyclerView. I'm using this layout to expand cardView in recyclerView: https://github.com/AAkira/ExpandableLayout .

If I click on some item to expand and than scroll down every 7th element is also expanded. What can I do to stop that effect? I know that this is caused beacause recyclerView remembers state of view holder and mIsViewExpanded in method onBindiewHolder is set to true every 7th item. Thanks in advance for any solutions!

Here is code of my viewholder:

class EventsViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

    private ImageView edit, cancel, expandIcon;

    private ExpandableLayout expandableArea;
    private boolean mIsViewExpanded = false;

    public EventsViewHolder(View itemView) {
        super(itemView);
        expandIcon = (ImageView) itemView.findViewById(R.id.card_item_expand_icon);
        expandIcon.setOnClickListener(this);
        //divider = (View) itemView.findViewById(R.id.event_card_divider);
    }

    private void collapseArea(){
        expandableArea.collapse();
        edit.setVisibility(View.GONE);
        cancel.setVisibility(View.GONE);
        mIsViewExpanded = false;
        expandIcon.setImageResource(R.drawable.vector_drawable_ic_expand_more_black___px);
    }

    private void expandArea(){
        expandableArea.expand();
        edit.setVisibility(View.VISIBLE);
        cancel.setVisibility(View.VISIBLE);
        mIsViewExpanded = true;
        expandIcon.setImageResource(R.drawable.vector_drawable_ic_expand_less_black___px);
    }

    @Override
    public void onClick(View view) {
        if(mIsViewExpanded){
            collapseArea();
            isItemExpanded[getAdapterPosition()] = false;
        }
        else {
            expandArea();
            isItemExpanded[getAdapterPosition()] = true;
        }
    }
}

When I click on icon inside CardView expandableArea is expanding or collapsing depending on mIsViewExpanded value.

EDIT. Adapter code:

public class EventsRecyclerViewAdapter extends RecyclerView.Adapter<EventsRecyclerViewAdapter.EventsViewHolder> implements PopupMenu.OnMenuItemClickListener, PopupMenu.OnDismissListener {

public interface EventsRecyclerViewAdapterInterface {
    public void emptyAdapter();

    public void itemDeleted(int id);
}

private List<Event> listData = new ArrayList<>();
private LayoutInflater inflater;
private MainActivity context;
private View view;
private int positionToCancel;

private boolean[] isItemExpanded;

private EventsRecyclerViewAdapterInterface deleteListener;

@Override
public boolean onMenuItemClick(MenuItem menuItem) {
    removeItem(positionToCancel);
    return true;
}

@Override
public void onDismiss(PopupMenu menu) {
    menu.dismiss();
}


public EventsRecyclerViewAdapter(Activity context, EventsRecyclerViewAdapterInterface deleteListener) {
    this.inflater = LayoutInflater.from(context);
    this.context = (MainActivity) context;
    this.deleteListener = deleteListener;
}

@Override
public EventsViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    view = inflater.inflate(R.layout.item_event_card, parent, false);
    return new EventsViewHolder(view);
}

@Override
public void onBindViewHolder(EventsViewHolder holder, int position) {
    Event item = listData.get(position);

    if(!isItemExpanded[position]){
        if(holder.mIsViewExpanded){
            holder.collapseArea();
        }
    }
    else {
        holder.expandArea();
    }

    holder.title.setText(item.getName());
    holder.place.setText(item.getLocation());

    DateTimeFormatter formatter = DateTimeFormat.forPattern(StaticValues.DATE_TIME_PATTERN_FOR_VIEW);
    holder.time.setText(formatter.print(item.getDate()));

    holder.edit.setOnClickListener(view1 -> takeDetailsEvent(item));
    holder.cancel.setOnClickListener(view1 -> {
        positionToCancel = position;

        MyPopupMenu popup = new MyPopupMenu(view1.getContext(), view1, context);
        popup.inflate(R.menu.event_delete_menu);
        popup.setOnMenuItemClickListener(this);
        popup.setOnDismissListener(this);
        popup.show();
    });

    holder.container.setOnClickListener(view1 -> {
        Intent intent = new Intent(view1.getContext(), EventDetailsActivity.class);
        intent.putExtra("eventId", item.getApiId());
        view1.getContext().startActivity(intent);
    });
}

private void removeItem(int position) {
    deleteListener.itemDeleted(listData.get(position).getApiId());
    listData.remove(position);
    notifyDataSetChanged();

    if (getItemCount() == 0) {
        deleteListener.emptyAdapter();
    }
}

private void takeDetailsEvent(Event event) {
    Intent intent = new Intent(view.getContext(), CreateEventActivity.class);
    intent.putExtra("id", event.getApiId());
    view.getContext().startActivity(intent);
}

public void setListData(List<Event> eventList) {
    listData = eventList;
    isItemExpanded = new boolean[listData.size()];
    notifyDataSetChanged();
}

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

}

This is how I deal with it right now. In adapter class I have boolean array which lenght is the same as listData. On position corresponding to list item I keep value: true when expanded, false when collapsed. In the onBindViewHolder I check if position is expanded and if not I collapse it. It's not the ideal solution because during scroll items are collapsing and it doesn't look good.

Zygi
  • 754
  • 8
  • 17
  • where is adapter class ? – Rahul Aug 23 '16 at 09:47
  • In the onBindViewHolder, in your adapter, you could standard collaps the ViewHolder you're binding to. That way, all new views are collapsed. – Marcel50506 Aug 23 '16 at 09:51
  • 1
    The items are recycled Views so every so often as you scroll an action on a specific item is repeated to other Items further UP or Down the list. What you can do is store an action for an item using (.setTag()) and retieve it using (.getTag()). Then its just a case of (if item.getTag() == 1 -- Expand else Dont expand) check on SO about set and get Tag for Adapter items – Tasos Aug 23 '16 at 09:52
  • I don't see you calling `collapseArea` anywhere and you've created `isItemExpanded` but you are not event using it. – Abbas Aug 23 '16 at 10:36
  • Upps sorry this was my array with items state. I'm not using it anymore but I forgot to delete that from code. Like I said with this solution when I remember state for every item cardsViews are collapsing during scroll and it looks weird. EDIT. Updated Adapter to contain my solution. – Zygi Aug 23 '16 at 10:45
  • 1
    Set `expandableView.setDuration(0)` and then collapse/expand the view and then reset duration. – Abbas Aug 23 '16 at 11:31
  • Hmm good idea. I will try it. But would like to try @Tasos solution. Could you explain more? – Zygi Aug 23 '16 at 12:07
  • 1
    You'll still get the same expand/collapse behavior with @Tasos's solution. Which is just a workaround for keeping `isItemExpanded[] ` yourself. – Abbas Aug 23 '16 at 12:20
  • Setting duration to 0 does the job! Thanks for you help @Abbas! – Zygi Aug 23 '16 at 14:26
  • I think the recycled view needs an upgrade. The ideal solution is to Auto remember a change for an item and not affect other items. – Tasos Aug 23 '16 at 15:00
  • @Zibiksior Please check answer of this link: http://stackoverflow.com/questions/40125509/android-recycler-view-row-item-duplicates-on-scroll-while-view-expanding-relativ/40127201#40127201 OR http://stackoverflow.com/questions/37629004/selected-image-button-changes-its-position-in-recyclerview-when-scrolling/37630203#37630203 It is the standard way not work around – Gaurav Singh Oct 19 '16 at 09:38

3 Answers3

2

RecyclerView doesn't create new views instead it reuses the existing views when scrolling (ScrappedViews). So what is happening is when you expand a CardView and scroll, since the CardViews are reused you get another expanded view.

So simply collapse/expand the view in the onBindView() of your Adapter, for that you'll need to associate a state with each item in adapter's list.

To resolve the expand/collapse animation during scrolling simply setDuration(0) which allows you to expand/collapse without any delay or animation for smoother scrolling experience.

Abbas
  • 3,529
  • 5
  • 36
  • 64
  • Yes I am aware of that RecyclerView is using ScrappedViews thats why I'm asking what can I do with that. I tried saving state of every item on the list. I made a boolean array to remember state of each item in recyclerview. But when I was scrolling items were collapsing during scroll and it looked weird. Also when I callaps view every time in onBindViewHolder items are collapsing during scroll and it's not ideal solution. – Zygi Aug 23 '16 at 10:18
  • @Zibiksior Then please add your `Adapter`' in your question. – Abbas Aug 23 '16 at 10:20
2

Override these 2 methods in your adapter.

@Override
public long getItemId(int position) {
return position;
}

@Override
public int getItemViewType(int position) {
return position;
} 

Reference = How to prevent items from getting duplicated when scrolling recycler view

drojokef
  • 425
  • 1
  • 5
  • 15
0

It is expected behavior of recyclerView. RecyclerView recycles items that are not in view. if you do not want your items to be recycled use holder.setIsRecyclable(false); in your onBindViewHolder() method of your adapter.

holder.setIsRecyclable(false);
saty
  • 66
  • 7
  • I would say this is not a really good solution, since the `setTag` suggestion by @Tasos in the comments would solve the problem, while not affecting performance. – Marcel50506 Aug 23 '16 at 11:59
  • i have already specified if you do not want your items to be recycled than use this method. – saty Aug 23 '16 at 12:10