RecyclerView Adapter notifyDataSetChanged stops fancy animation

33,860

Solution 1

No, it is wrong. First of all, you cannot reference to the position passed to the onBindViewHolder after that method returns. RecyclerView will not rebind a view when its position changes (due to items moving etc).

Instead, you can use ViewHolder#getPosition() which will return you the updated position.

If you fix that, your move code should work & provide nice animations.

Calling notifyDataSetChanged will prevent predictive animations so avoid it as long as you can. See documentation for details.

Edit (from comment): to get position from the outside, get child view holder from recyclerview and then get position from the vh. See RecyclerView api for details

Solution 2

There is a way to preserve fancy animations with just notifyDataSetChanged()

  1. You need to make your own GridLayoutManager with overriden supportsPredictiveItemAnimations() method returning true;

  2. You need to mAdapter.setHasStableIds(true)

  3. The part I find tricky is you need to override you adapter's getItemId() method. It should return value that is truly unique and not a direct function of position. Something like mItems.get(position).hashCode()

Worked perfectly fine in my case - beautiful animations for adding, removing and moving items only using notifyDataSetChanged()

Solution 3

1) You'll use notifyItemInserted(position); or notifyItemRemoved(position); instead of notifyDataSetChanged() for animation. 2) You can just manually fix your problem - using

public void move(int from, int to){
    Track track = mArray.remove(from);
    mArray.add(to, track);
    notifyItemMoved(from, to);
    ViewHolder fromHolder = (ViewHolder) mRecyclerView.findViewHolderForPosition(from);
    ViewHolder toHolder = (ViewHolder) mRecyclerView.findViewHolderForPosition(to);
    Tag fromTag = fromHolder.itemView.getTag();
    fromHolder.itemView.setTag(toHolder.itemView.getTag()); 
    toHolder.itemView.setTag(fromTag);

}
Share:
33,860
elgui
Author by

elgui

Updated on July 08, 2020

Comments

  • elgui
    elgui almost 4 years

    I am building a component based on RecyclerView, allowing user to reorder items by drag and drop. Once I am on the DragListener side, I need the position it has in the adapter in order to perform correct move, but I only have access to the view. So here is what I am doing in the adapter view binding :

    @Override
    public void onBindViewHolder(ViewHolder viewHolder, int position) {
        Track track = mArray.get(position);
        viewHolder.itemView.setTag(R.string.TAG_ITEM_POSITION, position);
    }
    

    Does it seem correct to you ? Because if I move an item like this :

    public void move(int from, int to){
        Track track = mArray.remove(from);
        mArray.add(to, track);
        notifyItemMoved(from, to);
    }
    

    then position tag is not correct anymore, and if I notifyDataSetChanged(), I lose the fancy animation. Any suggestion ?

  • elgui
    elgui over 9 years
    ok, thank you for this answer =) but now what is the best way to retrieve the position from outside the adpater (my dragListener for example) ?
  • yigit
    yigit over 9 years
    Get child view holder from recyclerview and then get position from the vh. See RecyclerView api for details
  • M. Erfan Mowlaei
    M. Erfan Mowlaei about 9 years
    You saved me a lot of pain man, tyvm. BTW this should be accepted answer not the one above.
  • M. Erfan Mowlaei
    M. Erfan Mowlaei about 9 years
    and I forgot to say, if you are using DB to feed the adapter something like primary key for part 3 will solve your problem.
  • worked
    worked over 8 years
    1. notifyItemChanged(position)
  • Admin
    Admin over 8 years
    Hi, I don't understand what you need use "mItems.get(position).hashCode()" returns long I need return a object .., can you tell me ? and how return object ? thx
  • tochkov
    tochkov over 8 years
    It is for your getItemId (int position) method which returns long.
  • jj.
    jj. about 8 years
    getPosition() was deprecated because it's too ambiguous. In this case you'll want to use getLayoutPosition()
  • kirtan403
    kirtan403 about 8 years
    That no 3 was something that turned my app Happy !! :) It can not control its emotions..
  • Daniel López Lacalle
    Daniel López Lacalle almost 8 years
    I love you. Like seriously :D
  • urSus
    urSus over 7 years
    When removing, the view which was offscreen and should now slide in-screen just fades in, any way to make it slide instead?
  • user25
    user25 over 7 years
    but mAdapter.notifyItemMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition()); sucks. I see that items was moved Collections.swap(....,...) but when I click on them they are old ones (so only titles of items were changed)
  • alexbirkett
    alexbirkett about 7 years
    Not sure this is such a great idea because unequal objects can have the same hash codes eclipsesource.com/blogs/2012/09/04/…
  • HiddenDroid
    HiddenDroid almost 7 years
    Thank you @tochkov for the answer. It saved my life. Can you please give me some details about how this make it work? Or just refer me to a link or a video. Thx again
  • user3354265
    user3354265 about 6 years
    @tochkov Thanks a lot. you deserve a medal :)