Currently, by using the default animator android.support.v7.widget.DefaultItemAnimator
, here's the outcome I'm having during sorting
DefaultItemAnimator animation video : https://youtu.be/EccI7RUcdbg
public void sortAndNotifyDataSetChanged() {
int i0 = 0;
int i1 = models.size() - 1;
while (i0 < i1) {
DemoModel o0 = models.get(i0);
DemoModel o1 = models.get(i1);
models.set(i0, o1);
models.set(i1, o0);
i0++;
i1--;
//break;
}
// adapter is created via adapter = new RecyclerViewDemoAdapter(models, mRecyclerView, this);
adapter.notifyDataSetChanged();
}
However, instead of the default animation during sorting (notifyDataSetChanged), I prefer to provide custom animation as follow. Old item will slide out via right side, and new item will slide up.
Expected animation video : https://youtu.be/9aQTyM7K4B0
Few years ago, I achieve this effect by using LinearLayout
+ View
, as that time, we don't have RecyclerView
yet.
This is how the animation is being setup
PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f, 0f);
PropertyValuesHolder translationX = PropertyValuesHolder.ofFloat("translationX", 0f, (float) width);
ObjectAnimator animOut = ObjectAnimator.ofPropertyValuesHolder(this, alpha, translationX);
animOut.setDuration(duration);
animOut.setInterpolator(accelerateInterpolator);
animOut.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator anim) {
final View view = (View) ((ObjectAnimator) anim).getTarget();
Message message = (Message)view.getTag(R.id.TAG_MESSAGE_ID);
if (message == null) {
return;
}
view.setAlpha(0f);
view.setTranslationX(0);
NewsListFragment.this.refreshUI(view, message);
final Animation animation = AnimationUtils.loadAnimation(NewsListFragment.this.getActivity(),
R.anim.slide_up);
animation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
view.setVisibility(View.VISIBLE);
view.setTag(R.id.TAG_MESSAGE_ID, null);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
view.startAnimation(animation);
}
});
layoutTransition.setAnimator(LayoutTransition.DISAPPEARING, animOut);
this.nowLinearLayout.setLayoutTransition(layoutTransition);
and, this is how the animation is being triggered.
// messageView is view being added earlier in nowLinearLayout
for (int i = 0, ei = messageViews.size(); i < ei; i++) {
View messageView = messageViews.get(i);
messageView.setTag(R.id.TAG_MESSAGE_ID, messages.get(i));
messageView.setVisibility(View.INVISIBLE);
}
I was wondering, how I can achieve the same effect in RecylerView?
Here is one more direction you can look at, if you don't want your scroll to reset on each sort (GITHUB demo project):
Use some kind of RecyclerView.ItemAnimator
, but instead of rewriting animateAdd()
and animateRemove()
functions, you can implement animateChange()
and animateChangeImpl()
. After sort you can call adapter.notifyItemRangeChanged(0, mItems.size());
to triger animation.
So code to trigger animation will look pretty simple:
for (int i = 0, j = mItems.size() - 1; i < j; i++, j--)
Collections.swap(mItems, i, j);
adapter.notifyItemRangeChanged(0, mItems.size());
For animation code you can use android.support.v7.widget.DefaultItemAnimator
, but this class has private animateChangeImpl()
so you will have to copy-pasted code and changed this method or use reflection. Or you can create your own ItemAnimator
class like @Andreas Wenger
did in his example of SlidingAnimator
. The point here is to implement animateChangeImpl
Simmilar to your code there are 2 animations:
1) Slide old view to the right
private void animateChangeImpl(final ChangeInfo changeInfo) {
final RecyclerView.ViewHolder oldHolder = changeInfo.oldHolder;
final View view = oldHolder == null ? null : oldHolder.itemView;
final RecyclerView.ViewHolder newHolder = changeInfo.newHolder;
final View newView = newHolder != null ? newHolder.itemView : null;
if (view == null) return;
mChangeAnimations.add(oldHolder);
final ViewPropertyAnimatorCompat animOut = ViewCompat.animate(view)
.setDuration(getChangeDuration())
.setInterpolator(interpolator)
.translationX(view.getRootView().getWidth())
.alpha(0);
animOut.setListener(new VpaListenerAdapter() {
@Override
public void onAnimationStart(View view) {
dispatchChangeStarting(oldHolder, true);
}
@Override
public void onAnimationEnd(View view) {
animOut.setListener(null);
ViewCompat.setAlpha(view, 1);
ViewCompat.setTranslationX(view, 0);
dispatchChangeFinished(oldHolder, true);
mChangeAnimations.remove(oldHolder);
dispatchFinishedWhenDone();
// starting 2-nd (Slide Up) animation
if (newView != null)
animateChangeInImpl(newHolder, newView);
}
}).start();
}
2) Slide up new view
private void animateChangeInImpl(final RecyclerView.ViewHolder newHolder,
final View newView) {
// setting starting pre-animation params for view
ViewCompat.setTranslationY(newView, newView.getHeight());
ViewCompat.setAlpha(newView, 0);
mChangeAnimations.add(newHolder);
final ViewPropertyAnimatorCompat animIn = ViewCompat.animate(newView)
.setDuration(getChangeDuration())
.translationY(0)
.alpha(1);
animIn.setListener(new VpaListenerAdapter() {
@Override
public void onAnimationStart(View view) {
dispatchChangeStarting(newHolder, false);
}
@Override
public void onAnimationEnd(View view) {
animIn.setListener(null);
ViewCompat.setAlpha(newView, 1);
ViewCompat.setTranslationY(newView, 0);
dispatchChangeFinished(newHolder, false);
mChangeAnimations.remove(newHolder);
dispatchFinishedWhenDone();
}
}).start();
}
Here is demo image with working scroll and kinda similar animation https://i.gyazo.com/04f4b767ea61569c00d3b4a4a86795ce.gif https://i.gyazo.com/57a52b8477a361c383d44664392db0be.gif
Edit:
To speed up RecyclerView preformance, instead of adapter.notifyItemRangeChanged(0, mItems.size());
you probably would want to use something like:
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
int firstVisible = layoutManager.findFirstVisibleItemPosition();
int lastVisible = layoutManager.findLastVisibleItemPosition();
int itemsChanged = lastVisible - firstVisible + 1;
// + 1 because we start count items from 0
adapter.notifyItemRangeChanged(firstVisible, itemsChanged);