Currently working on implementing an "encyclopedia" of sorts via a RecyclerView that I can sort, filter, search, etc. Functionally, I have it working fine, so I was getting started on animations. I only want animations when the data set is changed, not on scrolling or touch events or etc., so I'm just using an ItemAnimator.
Well, many of the ways my RecyclerView can be sorted, filtered, or searched result in virtually or literally the entire data set being replaced. I figured these would be the easiest cases to animate, since in this case I can just call notifyDataSetRemoved(0, oldItemCount)
followed by notifyDataSetInserted(0, newItemCount)
, which would call animateRemove()
and animateAdd()
respectively on each item modified. And that's exactly what happens! In a very simple sense, it works, I guess. But it just so happens that it only works properly once.
Here's the remove animation (currently):
public boolean animateRemove(RecyclerView.ViewHolder holder) {
holder.itemView.clearAnimation();
holder.itemView.animate()
.alpha(0)
.setInterpolator(new AccelerateInterpolator(2.f))
.setDuration(350)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
dispatchRemoveFinished(holder);
}
})
.start();
return false;
}
and the add animation (currently):
public boolean animateAdd(RecyclerView.ViewHolder holder) {
holder.itemView.clearAnimation();
final int screenHeight = Resources.getSystem().getDisplayMetrics().heightPixels;
holder.itemView.setTranslationY(screenHeight);
holder.itemView.animate()
.translationY(0)
.setInterpolator(new DecelerateInterpolator(3.f))
.setDuration(650)
.setStartDelay(450 + holder.getLayoutPosition() * 75)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
dispatchAddFinished(holder);
}
})
.start();
return false;
}
and an example of what code that calls it would look like:
public void filterDataSetExample() {
int oldItemCount = getItemCount();
// ... logic that changes the data set ...
notifyItemRangeRemoved(0, oldItemCount);
notifyItemRangeInserted(0, getItemCount());
}
The first time I do something that calls this sequence, the remove animation plays smoothly followed by the add animations. The problem comes when I try to do it a second time.
On the second time, and any times following, the two animations become "merged" somehow. This is basically what happens (it's the same every time):
The remove animation picks up the delay (base + staggered) from the add animation. This means that the animations are basically playing at the exact same time, which is bad enough already.
The add animation picks up the alpha shift from the remove animation, but only for the ViewHolders on the top 75% or so of the screen. The bottom 2-3 are still visible just fine. But overall, this means 70-80% of my ViewHolders are invisible, throughout my entire list (since they get recycled). At first I thought they disappeared entirely somehow, but if I change the alpha shift to a non-0 value like
0.25f
I can see the holders there, transparent.On top of those two things - if I completely re-populate the RecyclerView and simply call
notifyDataSetChanged()
, which I have no animations set up for currently, some of the invisible ViewHolders will gradually become visible again (2-3 per time, until all of them are back to normal visibility).Then, if I call
notifyDataSetChanged()
enough times to make the whole stack visible again, I'm right exactly back where I started! The animations will work fine for exactly ONE remove -> insert cycle, then merge properties again.
At first, I thought maybe the problem was reusing ViewHolders and them having the same itemView
somehow and the old animation still being attached. Which is why I have the holder.itemView.clearAnimation()
line at the beginning of each method, but it does nothing to the effect.
I'm stumped. Especially since the bottom 2 or so ViewHolders seem to be immune to the effect, despite presumably going through the exact same process as all the others. And it's always the ones at the bottom of the screen, regardless of where I'm scrolled to, so it isn't position-related.
I'm sure there's something (maybe many things) that I'm missing, however, since this is the first time I've worked extensively with an ItemAnimator.
Any help would be greatly appreciated.