Skip to content

Instantly share code, notes, and snippets.

@darnmason
Last active September 7, 2021 09:37
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save darnmason/f98d226d0279ac9e6da6df60897a1d1b to your computer and use it in GitHub Desktop.
Save darnmason/f98d226d0279ac9e6da6df60897a1d1b to your computer and use it in GitHub Desktop.
ItemAnimator for RecyclerView that animates changes between 2 states, edit and standard which is useful for multi select using check boxes. In edit mode a checkBox slides in from the left and an icon slides off screen to the right. In between an avatar and name view must adjust their positions. All 4 views are in a horizontal LinearLayout with w…
public class MultiSelectItemAnimator extends DefaultItemAnimator {
private static final long DURATION = 150;
private final ArrayMap<RecyclerView.ViewHolder, AnimatorInfo> animatorMap = new ArrayMap<>();
private final Animator.AnimatorListener resetListener = new ViewObjectAnimatorListener() {
@Override
public void onAnimationEnd(Animator animation, View view) {
view.setTranslationX(0f);
}
};
private final Animator.AnimatorListener hideListener = new ViewObjectAnimatorListener() {
@Override
public void onAnimationEnd(Animator animation, View view) {
view.setVisibility(View.GONE);
view.setTranslationX(0f);
}
@Override
public void onAnimationStart(Animator animation, View view) {
view.setVisibility(View.VISIBLE);
}
@Override
public void onAnimationCancel(Animator animation, View view) {
view.setTranslationX(0f);
// Prevent onAnimationEnd being called which would hide the view
animation.removeListener(this);
}
};
@Override
public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder) {
return viewHolder instanceof SelectableViewHolder;
}
@NonNull
@Override
public ItemHolderInfo recordPreLayoutInformation(@NonNull RecyclerView.State state,
@NonNull RecyclerView.ViewHolder viewHolder,
int changeFlags,
@NonNull List<Object> payloads
) {
MyItemHolderInfo info = (MyItemHolderInfo) super.recordPreLayoutInformation(state,
viewHolder,
changeFlags,
payloads
);
if (viewHolder instanceof SelectableViewHolder) {
info.inEdit = ((SelectableViewHolder) viewHolder).inEdit;
}
return info;
}
@NonNull
@Override
public ItemHolderInfo recordPostLayoutInformation(@NonNull RecyclerView.State state,
@NonNull RecyclerView.ViewHolder viewHolder
) {
MyItemHolderInfo info = (MyItemHolderInfo) super.recordPostLayoutInformation(state, viewHolder);
if (viewHolder instanceof SelectableViewHolder) {
info.inEdit = ((SelectableViewHolder) viewHolder).inEdit;
}
return info;
}
@Override
public ItemHolderInfo obtainHolderInfo() {
return new MyItemHolderInfo();
}
private static class MyItemHolderInfo extends ItemHolderInfo {
boolean inEdit;
}
@Override
public boolean animateChange(@NonNull final RecyclerView.ViewHolder oldHolder,
@NonNull final RecyclerView.ViewHolder newHolder,
@NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo
) {
MyItemHolderInfo oldInfo = (MyItemHolderInfo) preInfo;
MyItemHolderInfo newInfo = (MyItemHolderInfo) postInfo;
if (oldHolder != newHolder || !(oldHolder instanceof SelectableViewHolder) || oldInfo.inEdit == newInfo.inEdit) {
dispatchAnimationFinished(oldHolder);
dispatchAnimationFinished(newHolder);
return false;
}
final SelectableViewHolder viewHolder = (SelectableViewHolder) newHolder;
long prevAnimPlayTime = 0;
AnimatorInfo oldAnim = animatorMap.get(viewHolder);
if (oldAnim != null) {
prevAnimPlayTime = oldAnim.checkBoxAnim.getCurrentPlayTime();
oldAnim.overallAnim.cancel();
}
ObjectAnimator checkBoxAnim;
ObjectAnimator avatarAnim;
ObjectAnimator nameAnim;
ObjectAnimator iconAnim;
int distance = viewHolder.checkBox.getWidth();
if (newInfo.inEdit) {
checkBoxAnim = translateX(viewHolder.checkBox, -distance, 0, resetListener, prevAnimPlayTime);
avatarAnim = translateX(viewHolder.avatarView, -distance, 0, resetListener, prevAnimPlayTime);
nameAnim = translateX(viewHolder.nameView, -distance, 0, resetListener, prevAnimPlayTime);
iconAnim = translateX(viewHolder.iconView, 0, distance, hideListener, prevAnimPlayTime);
} else {
checkBoxAnim = translateX(viewHolder.checkBox, 0, -distance, hideListener, prevAnimPlayTime);
avatarAnim = translateX(viewHolder.avatarView, 0, -distance, resetListener, prevAnimPlayTime);
nameAnim = translateX(viewHolder.nameView, 0, -distance, resetListener, prevAnimPlayTime);
iconAnim = translateX(viewHolder.iconView, distance, 0, resetListener, prevAnimPlayTime);
}
AnimatorSet overallAnim = new AnimatorSet();
overallAnim.playTogether(checkBoxAnim, avatarAnim, nameAnim, iconAnim);
overallAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
dispatchAnimationFinished(viewHolder);
animatorMap.remove(viewHolder);
}
});
overallAnim.start();
animatorMap.put(viewHolder, new AnimatorInfo(overallAnim, checkBoxAnim));
return true;
}
private ObjectAnimator translateX(View view,
int from,
int to,
Animator.AnimatorListener listener,
long prevAnimPlayTime
) {
ObjectAnimator anim = ObjectAnimator.ofFloat(view, "translationX", (float) from, (float) to);
anim.addListener(listener);
anim.setDuration(DURATION);
if (prevAnimPlayTime > 0) {
anim.setCurrentPlayTime(DURATION - prevAnimPlayTime);
}
return anim;
}
@Override
public void endAnimation(RecyclerView.ViewHolder item) {
super.endAnimation(item);
if (animatorMap.containsKey(item)) {
animatorMap.remove(item).overallAnim.cancel();
dispatchAnimationFinished(item);
}
}
@Override
public boolean isRunning() {
return super.isRunning() || !animatorMap.isEmpty();
}
@Override
public void endAnimations() {
super.endAnimations();
if (!animatorMap.isEmpty()) {
final int numRunning = animatorMap.size();
for (int i = numRunning; i >= 0; i--) {
animatorMap.valueAt(i).overallAnim
.cancel();
dispatchAnimationFinished(animatorMap.keyAt(i));
}
animatorMap.clear();
}
}
private static class AnimatorInfo {
Animator overallAnim;
ObjectAnimator checkBoxAnim;
AnimatorInfo(Animator overallAnim, ObjectAnimator checkBoxAnim) {
this.overallAnim = overallAnim;
this.checkBoxAnim = checkBoxAnim;
}
}
}
public class ViewObjectAnimatorListener extends AnimatorListenerAdapter {
@Override
public void onAnimationCancel(Animator animation) {
View target = (View) ((ObjectAnimator) animation).getTarget();
if (target != null) {
onAnimationCancel(animation, target);
}
}
public void onAnimationCancel(Animator animation, View view) {
}
@Override
public void onAnimationEnd(Animator animation) {
View target = (View) ((ObjectAnimator) animation).getTarget();
if (target != null) {
onAnimationEnd(animation, target);
}
}
public void onAnimationEnd(Animator animation, View view) {
}
@Override
public void onAnimationRepeat(Animator animation) {
View target = (View) ((ObjectAnimator) animation).getTarget();
if (target != null) {
onAnimationRepeat(animation, target);
}
}
public void onAnimationRepeat(Animator animation, View view) {
}
@Override
public void onAnimationStart(Animator animation) {
View target = (View) ((ObjectAnimator) animation).getTarget();
if (target != null) {
onAnimationStart(animation, target);
}
}
public void onAnimationStart(Animator animation, View view) {
}
@Override
public void onAnimationPause(Animator animation) {
View target = (View) ((ObjectAnimator) animation).getTarget();
if (target != null) {
onAnimationPause(animation, target);
}
}
public void onAnimationPause(Animator animation, View view) {
}
@Override
public void onAnimationResume(Animator animation) {
View target = (View) ((ObjectAnimator) animation).getTarget();
if (target != null) {
onAnimationResume(animation, target);
}
}
public void onAnimationResume(Animator animation, View view) {
}
}
@sooryanb
Copy link

sooryanb commented Sep 7, 2021

Do you have an working example code for this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment