I have a RecyclerView
where I want to allow a user to swipe an item to the left or right for an action, but then have the item return. I implemented a solution found here, which works well. Currently, I have implemented just the swiping and returning, but no actions yet.
However, this has resulted in a visual oddity. When I swipe an item either left or right, the item below it will quickly slide down and back up. This only happens to the item below it and not the one above it, and the amount it slides seems random. Moreover, this "jump" from the item below only happens on the first swipe. I can swipe the same item again and again and it will not result in any more jumps from the item below. I can only replicate the jump by scrolling away from the item and returning to it.
Here is an example of this jump. The GIF isn't the best quality, but it was the best I could do... The GIF appears as if the item below is just disappearing & reappearing, but it is actually sliding down and back up really quickly. In this case, the jump is a bit extreme, but some jumps are much more subtle.
I use a MarginDecoration
to separate my items. I thought that perhaps it had something to do with it being destroyed, redrawn, something of the sort. However, I removed it and the jump still occurs. Additionally, I suspected the ImageView
, but removing it does not solve the issue.
To summarize:
MarginDecoration
or ImageView
does not prevent the issue.Why does this occur and how would I go about fixing it? Alternatively, perhaps the implementation is flawed? If so, how might one go about swiping and returning an item while maintaining the ability to invoke onSwiped()
for an action to occur & maintaining the position of the item above and below?
For reference, here is my RecyclerView
code:
private fun initRecyclerView() {
val swipeRefreshLayout: SwipeRefreshLayout = binding.articleSwipeRefreshLayout
swipeRefreshLayout.setOnRefreshListener {
viewModel.getNewsHeadlines()
swipeRefreshLayout.isRefreshing = false
}
val recyclerView: RecyclerView = binding.articleRecyclerView
val divider: Drawable? =
AppCompatResources.getDrawable(requireContext(), R.drawable.divider_white)
val decoration: MarginDecoration? = divider?.let { MarginDecoration(it) }
if (decoration != null) {
recyclerView.addItemDecoration(decoration)
}
recyclerView.layoutManager = LinearLayoutManager(requireContext())
adapter = ArticleAdapter(requireActivity())
recyclerView.adapter = adapter
ItemTouchHelper(
object : ItemTouchHelper.SimpleCallback(
0,
ItemTouchHelper.END or ItemTouchHelper.START
) {
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
adapter.notifyItemChanged(viewHolder.adapterPosition)
}
}
).attachToRecyclerView(recyclerView)
}
By default, the RecyclerView
has a RecyclerView.ItemAnimator
active on it. This will animate the ViewHolders
in four circumstances: added, removed, changed, and moved.
Here, two animations are happening:
adapter.notifyItemChanged(viewHolder.adapterPosition)
is called. This animation will return the item from the left or right of the screen back to its position in the RecyclerView
.I am not sure why the move animation is occurring, as the item is not being moved, but I know it occurs because if I set the moveDuration
of the animator to 0
, it no longer occurs. This would be the solution to prevent the item below the swiped one to bounce or jitter after calling adapter.notifyItemChanged(viewHolder.adapterPosition)
:
recyclerView.itemAnimator?.moveDuration = 0
However, if a move animation is desired in other use cases for this RecyclerView
, then you might consider creating a custom implementation of RecyclerView.ItemAnimator
that may fit the use case. Or, it may be possible to set moveDuration
to 0
right before calling adapter.notifyItemChanged(viewHolder.adapterPosition)
and then returning it to its default value, which is 250
, so that the move animation is retained for other circumstances.