Android RecyclerView ItemTouchHelper revert swipe and restore view holder

半世苍凉 提交于 2019-11-29 22:42:49

After some random poking I found a solution. Call notifyItemChanged on you adapter. This will make the swiped out view animate back into it's original position.

Google's ItemTouchHelper implementation assumes that every swiped out item will eventually get removed from the recycler view, whereas it might not be the case in some applications.

RecoverAnimation is a nested class in ItemTouchHelper that manages the touch animation of the swiped/dragged items. Although the name implies that it only recovers the position of items, it's actually the only class that is used to recover (cancel swipe/drag) and replace (move out on swipe or replace on drag) items. Strange naming.

There's a boolean property named mIsPendingCleanup in RecoverAnimation, which ItemTouchHelper uses to figure out whether the item is pending removal. So ItemTouchHelper, after attaching a RecoverAnimation to the item, sets this property after a successful swipe out, and the animation does not get removed from the list of recover animations as long as this property is set. The problem is that, mIsPendingCleanup will always be set for a swiped out item, causing the RecoverAnimation for the item to never be removed from the list of animations. So even if you recover the item's position after a successul swipe, it will be sent back to the swiped-out position as soon as you touch it - because the RecoverAnimation will cause the animation start from the latest swiped-out position.

Solution to this is unfortunately to copy the ItemTouchHelper class source code into the same package as it is in the support library, and remove the mIsPendingCleanup property from the RecoverAnimation class. I'm not sure if this is acceptable by Google, and I haven't posted the update to Play Store yet to see whether it will cause a reject, but you may find the class source code from support library v22.2.1 with the above mentioned fix at https://gist.github.com/kukabi/f46e1c0503d2806acbe2.

You should override onSwiped method in ItemTouchHelper.Callback and refresh that particular item.

 @Override
 public void onSwiped(RecyclerView.ViewHolder viewHolder,
     int direction) {
     adapter.notifyItemChanged(viewHolder.getAdapterPosition());
 }
Jan Bollacke

A dirty workaround solution for this problem is to re-attach the ItemTouchHelper by calling ItemTouchHelper::attachToRecyclerView(RecyclerView) twice, which then calls the private method ItemTouchHelper::destroyCallbacks(). destroyCallbacks() removes item decoration and all listeners but also clears all RecoverAnimations.

Note that we need to call itemTouchHelper.attachToRecyclerView(null) first to trick ItemTouchHelper into thinking that the second call to itemTouchHelper.attachToRecyclerView(recyclerView) is a new recycler view.

For further details take a look into the source code of ItemTouchHelper here.

Example of workaround:

RecyclerView recyclerView = findViewById(R.id.recycler_view);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(callback);

...
// Workaround to reset swiped out views
itemTouchHelper.attachToRecyclerView(null);
itemTouchHelper.attachToRecyclerView(recyclerView);

Consider it as a dirty workaround because this method uses internal, undocumented implementation detail of ItemTouchHelper.

Update:

From the documentation of ItemTouchHelper::attachToRecyclerView(RecyclerView):

If TouchHelper is already attached to a RecyclerView, it will first detach from the previous one. You can call this method with null to detach it from the current RecyclerView.

and in the parameters documentation:

The RecyclerView instance to which you want to add this helper or null if you want to remove ItemTouchHelper from the current RecyclerView.

So at least it is partly documented.

Call notifyDataSetChanged on your adapter to make the swipe back work consistent

In the case of using LiveData to provide a list to a ListAdapter, calling notifyItemChanged does not work. However, I found a fugly workaround which involves re-attaching the ItemTouchHelper to the recycler view in onSwiped callback as such

val recyclerView = someRecyclerViewInYourCode

var itemTouchHelper: ItemTouchHelper? = null

val itemTouchCallback = object : ItemTouchHelper.Callback {
    override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction:Int) {
        itemTouchHelper?.attachToRecyclerView(null)
        itemTouchHelper?.attachToRecyclerView(recyclerView)
    }
}

itemTouchHelper = ItemTouchHelper(itemTouchCallback)

itemTouchHelper.attachToRecyclerView(recyclerView)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!