12

I'm trying to make cardviews in recyclerview expand. I got the expanding part working, but when adding transition to it, some visual bugs start to occur. The transitions works fine, when there are no off-screen items, but when I add more than (in my case) 4 items to the recyclerview, it starts to occur.

GIF with 4 items

GIF with more than 4 items

The cardview expanding works fine with more than 4 items when I disable the transition animation. I think the problem is related to positions changing, but I can't find any solution to the problem.

The guide I used to implement the cardview expanding can be found here: https://stackoverflow.com/a/38623873/6673949

And my complete recyclerview adapter

public class BasketRecyclerAdapter extends RecyclerView.Adapter<BasketRecyclerAdapter.CustomViewHolder> {
private String letter;
private Context mContext;
private ColorGenerator generator = ColorGenerator.MATERIAL;
private List<Basket> baskets;
private int mExpandedPosition = -1;
private RecyclerView r1;

public BasketRecyclerAdapter(Context context, List<Basket> baskets, RecyclerView r1) {
    this.mContext = context;
    this.baskets = baskets;
    this.r1 = r1;

}

@Override
public CustomViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
    View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.basket_menu_item, null);
    CustomViewHolder viewHolder = new CustomViewHolder(view);
    return viewHolder;
}

@Override
public void onBindViewHolder(final BasketRecyclerAdapter.CustomViewHolder holder, final int position) {

    String basketName = baskets.get(position).getBasketName();

    holder.basketName.setText(basketName);

    letter = "" + basketName.charAt(0);

    TextDrawable drawable = TextDrawable.builder()
            .buildRound(letter, generator.getColor(basketName));


    holder.imageLetter.setImageDrawable(drawable);

    final boolean isExpanded = position == mExpandedPosition;
    holder.expandedLayout.setVisibility(isExpanded?View.VISIBLE:View.GONE);
    holder.itemView.setActivated(isExpanded);
    holder.itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            mExpandedPosition = isExpanded ? -1:position;
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                TransitionManager.beginDelayedTransition(r1);
            }
            notifyDataSetChanged();
        }
    });


}

What can be done to solve the problem? Thanks.

Edit: I tried to get it to work by using just ListView instead of RecyclerView, but ListView adapter doesn't expand with same code - will try to figure out why.

Edit2: Got it working by using another library called "ExpandableLayout" but still can't seem to figure out why is it not working without additional libraries.

Community
  • 1
  • 1
andreas
  • 123
  • 2
  • 5
  • Consider **TransitionManager.beginDelayedTransition(r1);** to **TransitionManager.beginDelayedTransition(__);** – Vishnu T B Mar 18 '19 at 09:10
  • I use TransitionManager but when animate close in recyclerview, the previous items pile up before the animation is actually finished – Sasmita Mar 22 '22 at 06:36

3 Answers3

4

I had similar issue. Add in adapter:

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

And myAdapter.setHasStableIds(true);

manao
  • 328
  • 5
  • 12
4

I had the same problem. This is how I worked it out and works like you showed on GIF with 4 items:)

In your custom adapter add notifyItemChanged(position) and notifyItemChanged(previousExpandedPosition) instead of notifyDataSetChanged():

public void onBindViewHolder(@NonNull ViewHolder holder, final int position) {
    final boolean isExpanded = position==mExpandedPosition;

    holder.expandedLayout.setVisibility(isExpanded ? View.VISIBLE : View.GONE);
    holder.itemView.setActivated(isExpanded);

    if (isExpanded)
        mPreviousExpandedPosition = position;

    holder.itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            mExpandedPosition = isExpanded ? -1:position;
            TransitionManager.beginDelayedTransition(mRecyclerView);

            notifyItemChanged(mPreviousExpandedPosition);
            notifyItemChanged(position);
        }
    });
}

Declare global variables mExpandedPosition and mPreviousExpandedPosition and initialize them to -1.

Pass the mRecyclerView as a parameter in the constructor for the adapter.

In your fragment or activity set mRecyclerView.setItemAnimator(null) after setting adapter for recycle view, like this:

mRecyclerView.setAdapter(mRecyclerViewAdapter);
mRecyclerView.setItemAnimator(null);

This should solve your problem.

wast
  • 878
  • 9
  • 27
patonjo
  • 440
  • 4
  • 13
2

I had a similar issue. When there were no items offscreen it worked fine, but when you have items offscreen the animation does not work properly. The solution was to not update every item in the adapter using notifyDataSetChanged(). You need to only update the position you want to expand AND the position you want to collapse. My guess is that the ViewHolders for items off screen are messing up the animation.

    final boolean isExpanded = position == mExpandedPosition;
    vh.expandableView.setVisibility(isExpanded ? View.VISIBLE : View.GONE);
    vh.itemView.setActivated(isExpanded);
    vh.itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            // collapse any currently expanded items
            if (mExpandedPosition != RecyclerView.NO_POSITION) {
                notifyItemChanged(mExpandedPosition);
            }

            //Update expanded position
            mExpandedPosition = isExpanded ? -1 : position;

            TransitionManager.beginDelayedTransition(mRecyclerView);

            //Expand new item clicked
            notifyItemChanged(position);

        }
    });
Chris Hare
  • 291
  • 2
  • 5
  • Ugh, this took me forever to find. This solution isn't perfect because the changed item still fades and reappears but it does work. Annoying that it works perfectly for views not on the screen. Also, don't forget to notifyItemChanged(oldPosition) if oldPosition != -1 – easycheese Jan 03 '17 at 05:35