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 ?
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
Answer:
There is a way to preserve fancy animations with just notifyDataSetChanged()
-
You need to make your own
GridLayoutManager
with overridensupportsPredictiveItemAnimations()
method returningtrue
; -
You need to
mAdapter.setHasStableIds(true)
-
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 ofposition
. Something likemItems.get(position).hashCode()
Worked perfectly fine in my case – beautiful animations for adding, removing and moving items only using notifyDataSetChanged()
Answer:
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);
}
Answer:
You should move your method to OnCreateViewHolder
, then notifyItemRemoved(index)
works properly.
Answer:
I’m able to maintain the touch animations by adding this to my list item’s outer element
<View
android:foreground="?android:attr/selectableItemBackground"
...>
Answer:
I fixed it with using ‘notifyItemChanged(int position);’ instead of ‘notifyDataSetChanged();’
My adapter shows fancy animations perfectly and without any lags
Edit: I got position from onBindViewHolder’s position.
Tags: androidandroid, animation, view