The RecyclerView
, unlike to ListView
, doesn't have a simple way to set an empty view to it, so one has to manage it manually, making empty view visible in case of adapter's item count is 0.
Implementing this, at first I tried to call empty view logic right after modifying underlaying structure (ArrayList
in my case), for example:
btnRemoveFirst.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
devices.remove(0); // remove item from ArrayList
adapter.notifyItemRemoved(0); // notify RecyclerView's adapter
updateEmptyView();
}
});
It does the thing, but has a drawback: when the last element is being removed, empty view appears before animation of removing is finished, immediately after removal. So I decided to wait until end of animation and then update UI.
To my surprise, I couldn't find a good way to listen for animation events in RecyclerView. First thing coming to mind is to use isRunning
method like this:
btnRemoveFirst.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
devices.remove(0); // remove item from ArrayList
adapter.notifyItemRemoved(0); // notify RecyclerView's adapter
recyclerView.getItemAnimator().isRunning(new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
@Override
public void onAnimationsFinished() {
updateEmptyView();
}
});
}
});
Unfortunately, callback in this case runs immediately, because at that moment inner ItemAnimator
still isn't in the "running" state. So, the questions are: how to properly use ItemAnimator.isRunning() method and is there a better way to achieve the desired result, i.e. show empty view after removal animation of the single element is finished?
I have a little bit more generic case where I want to detect when the recycler view have finished animating completely when one or many items are removed or added at the same time.
I've tried Roman Petrenko's answer, but it does not work in this case. The problem is that
onAnimationFinished
is called for each entry in the recycler view. Most entries have not changed soonAnimationFinished
is called more or less instantaneous. But for additions and removals the animation takes a little while so there it's called later.This leads to at least two problems. Assume you have a method called
doStuff()
that you want to run when the animation is done.If you simply call
doStuff()
inonAnimationFinished
you will call it once for every item in the recycler view which might not be what you want to do.If you just call
doStuff()
the first timeonAnimationFinished
is called you may be calling this long before the last animation has been completed.If you could know how many items there are to be animated you could make sure you call
doStuff()
when the last animation finishes. But I have not found any way of knowing how many remaining animations there are queued up.My solution to this problem is to let the recycler view first start animating by using
new Handler().post()
, then set up a listener withisRunning()
that is called when the animation is ready. After that it repeats the process until all views have been animated.What worked for me is the following:
dispatchAnimationsFinished()
is calledupdateEmptyView()
)Currently the only working way I've found to solve this problem is to extend
ItemAnimator
and pass it toRecyclerView
like this:But this technique is not universal, because I have to extend from concrete
ItemAnimator
implementation being used byRecyclerView
. In case of private innerCoolItemAnimator
insideCoolRecyclerView
, my method will not work at all.PS: My colleague suggested to wrap
ItemAnimator
inside the decorator in a following manner:It would be nice, despite seems like overkill for a such trivial task, but creating the decorator in this case is not possible anyway, because
ItemAnimator
has a methodsetListener()
which is package protected so I obviously can't wrap it, as well as several final methods.To expand on Roman Petrenko's answer, I don't have a truly universal answer either, but I did find the Factory pattern to be a helpful way to at least clean up some of the cruft that is this issue.
In my case, I'm using a library which provides a FadeInAnimator that I was already using. I use Roman's solution in the factory method to hook into the onAnimationEnded event, then pass the event back up the chain.
Then, when I'm configuring my recyclerview, I specify the callback to be my method for updating the view based on the recyclerview item count:
Again, it's not totally universal across all any and all ItemAnimators, but it at least "consolidates the cruft", so if you have multiple different item animators, you can just implement a factory method here following the same pattern, and then your recyclerview configuration is just specifying which ItemAnimator you want.