Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save esskar/0c340157cdf09755112cfa21ea371242 to your computer and use it in GitHub Desktop.
Save esskar/0c340157cdf09755112cfa21ea371242 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) {
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment