-
-
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…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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