package io.github.froger.instamaterial.ui.adapter; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.support.annotation.NonNull; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.RecyclerView; import android.view.View; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import android.view.animation.OvershootInterpolator; import java.util.HashMap; import java.util.List; import java.util.Map; import io.github.froger.instamaterial.R; import io.github.froger.instamaterial.Utils; /** * Created by Miroslaw Stanek on 02.12.2015. */ public class FeedItemAnimator extends DefaultItemAnimator { private static final DecelerateInterpolator DECCELERATE_INTERPOLATOR = new DecelerateInterpolator(); private static final AccelerateInterpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator(); private static final OvershootInterpolator OVERSHOOT_INTERPOLATOR = new OvershootInterpolator(4); Map<RecyclerView.ViewHolder, AnimatorSet> likeAnimationsMap = new HashMap<>(); Map<RecyclerView.ViewHolder, AnimatorSet> heartAnimationsMap = new HashMap<>(); private int lastAddAnimatedItem = -2; @Override public boolean canReuseUpdatedViewHolder(RecyclerView.ViewHolder viewHolder) { return true; } @NonNull @Override public ItemHolderInfo recordPreLayoutInformation(@NonNull RecyclerView.State state, @NonNull RecyclerView.ViewHolder viewHolder, int changeFlags, @NonNull List<Object> payloads) { if (changeFlags == FLAG_CHANGED) { for (Object payload : payloads) { if (payload instanceof String) { return new FeedItemHolderInfo((String) payload); } } } return super.recordPreLayoutInformation(state, viewHolder, changeFlags, payloads); } @Override public boolean animateAdd(RecyclerView.ViewHolder viewHolder) { if (viewHolder.getItemViewType() == FeedAdapter.VIEW_TYPE_DEFAULT) { if (viewHolder.getLayoutPosition() > lastAddAnimatedItem) { lastAddAnimatedItem++; runEnterAnimation((FeedAdapter.CellFeedViewHolder) viewHolder); return false; } } dispatchAddFinished(viewHolder); return false; } private void runEnterAnimation(final FeedAdapter.CellFeedViewHolder holder) { final int screenHeight = Utils.getScreenHeight(holder.itemView.getContext()); holder.itemView.setTranslationY(screenHeight); holder.itemView.animate() .translationY(0) .setInterpolator(new DecelerateInterpolator(3.f)) .setDuration(700) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { dispatchAddFinished(holder); } }) .start(); } @Override public boolean animateChange(@NonNull RecyclerView.ViewHolder oldHolder, @NonNull RecyclerView.ViewHolder newHolder, @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) { cancelCurrentAnimationIfExists(newHolder); if (preInfo instanceof FeedItemHolderInfo) { FeedItemHolderInfo feedItemHolderInfo = (FeedItemHolderInfo) preInfo; FeedAdapter.CellFeedViewHolder holder = (FeedAdapter.CellFeedViewHolder) newHolder; animateHeartButton(holder); updateLikesCounter(holder, holder.getFeedItem().likesCount); if (FeedAdapter.ACTION_LIKE_IMAGE_CLICKED.equals(feedItemHolderInfo.updateAction)) { animatePhotoLike(holder); } } return false; } private void cancelCurrentAnimationIfExists(RecyclerView.ViewHolder item) { if (likeAnimationsMap.containsKey(item)) { likeAnimationsMap.get(item).cancel(); } if (heartAnimationsMap.containsKey(item)) { heartAnimationsMap.get(item).cancel(); } } private void animateHeartButton(final FeedAdapter.CellFeedViewHolder holder) { AnimatorSet animatorSet = new AnimatorSet(); ObjectAnimator rotationAnim = ObjectAnimator.ofFloat(holder.btnLike, "rotation", 0f, 360f); rotationAnim.setDuration(300); rotationAnim.setInterpolator(ACCELERATE_INTERPOLATOR); ObjectAnimator bounceAnimX = ObjectAnimator.ofFloat(holder.btnLike, "scaleX", 0.2f, 1f); bounceAnimX.setDuration(300); bounceAnimX.setInterpolator(OVERSHOOT_INTERPOLATOR); ObjectAnimator bounceAnimY = ObjectAnimator.ofFloat(holder.btnLike, "scaleY", 0.2f, 1f); bounceAnimY.setDuration(300); bounceAnimY.setInterpolator(OVERSHOOT_INTERPOLATOR); bounceAnimY.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { holder.btnLike.setImageResource(R.drawable.ic_heart_red); } @Override public void onAnimationEnd(Animator animation) { heartAnimationsMap.remove(holder); dispatchChangeFinishedIfAllAnimationsEnded(holder); } }); animatorSet.play(bounceAnimX).with(bounceAnimY).after(rotationAnim); animatorSet.start(); heartAnimationsMap.put(holder, animatorSet); } private void updateLikesCounter(FeedAdapter.CellFeedViewHolder holder, int toValue) { String likesCountTextFrom = holder.tsLikesCounter.getResources().getQuantityString( R.plurals.likes_count, toValue - 1, toValue - 1 ); holder.tsLikesCounter.setCurrentText(likesCountTextFrom); String likesCountTextTo = holder.tsLikesCounter.getResources().getQuantityString( R.plurals.likes_count, toValue, toValue ); holder.tsLikesCounter.setText(likesCountTextTo); } private void animatePhotoLike(final FeedAdapter.CellFeedViewHolder holder) { holder.vBgLike.setVisibility(View.VISIBLE); holder.ivLike.setVisibility(View.VISIBLE); holder.vBgLike.setScaleY(0.1f); holder.vBgLike.setScaleX(0.1f); holder.vBgLike.setAlpha(1f); holder.ivLike.setScaleY(0.1f); holder.ivLike.setScaleX(0.1f); AnimatorSet animatorSet = new AnimatorSet(); ObjectAnimator bgScaleYAnim = ObjectAnimator.ofFloat(holder.vBgLike, "scaleY", 0.1f, 1f); bgScaleYAnim.setDuration(200); bgScaleYAnim.setInterpolator(DECCELERATE_INTERPOLATOR); ObjectAnimator bgScaleXAnim = ObjectAnimator.ofFloat(holder.vBgLike, "scaleX", 0.1f, 1f); bgScaleXAnim.setDuration(200); bgScaleXAnim.setInterpolator(DECCELERATE_INTERPOLATOR); ObjectAnimator bgAlphaAnim = ObjectAnimator.ofFloat(holder.vBgLike, "alpha", 1f, 0f); bgAlphaAnim.setDuration(200); bgAlphaAnim.setStartDelay(150); bgAlphaAnim.setInterpolator(DECCELERATE_INTERPOLATOR); ObjectAnimator imgScaleUpYAnim = ObjectAnimator.ofFloat(holder.ivLike, "scaleY", 0.1f, 1f); imgScaleUpYAnim.setDuration(300); imgScaleUpYAnim.setInterpolator(DECCELERATE_INTERPOLATOR); ObjectAnimator imgScaleUpXAnim = ObjectAnimator.ofFloat(holder.ivLike, "scaleX", 0.1f, 1f); imgScaleUpXAnim.setDuration(300); imgScaleUpXAnim.setInterpolator(DECCELERATE_INTERPOLATOR); ObjectAnimator imgScaleDownYAnim = ObjectAnimator.ofFloat(holder.ivLike, "scaleY", 1f, 0f); imgScaleDownYAnim.setDuration(300); imgScaleDownYAnim.setInterpolator(ACCELERATE_INTERPOLATOR); ObjectAnimator imgScaleDownXAnim = ObjectAnimator.ofFloat(holder.ivLike, "scaleX", 1f, 0f); imgScaleDownXAnim.setDuration(300); imgScaleDownXAnim.setInterpolator(ACCELERATE_INTERPOLATOR); animatorSet.playTogether(bgScaleYAnim, bgScaleXAnim, bgAlphaAnim, imgScaleUpYAnim, imgScaleUpXAnim); animatorSet.play(imgScaleDownYAnim).with(imgScaleDownXAnim).after(imgScaleUpYAnim); animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { likeAnimationsMap.remove(holder); resetLikeAnimationState(holder); dispatchChangeFinishedIfAllAnimationsEnded(holder); } }); animatorSet.start(); likeAnimationsMap.put(holder, animatorSet); } private void dispatchChangeFinishedIfAllAnimationsEnded(FeedAdapter.CellFeedViewHolder holder) { if (likeAnimationsMap.containsKey(holder) || heartAnimationsMap.containsKey(holder)) { return; } dispatchAnimationFinished(holder); } private void resetLikeAnimationState(FeedAdapter.CellFeedViewHolder holder) { holder.vBgLike.setVisibility(View.INVISIBLE); holder.ivLike.setVisibility(View.INVISIBLE); } @Override public void endAnimation(RecyclerView.ViewHolder item) { super.endAnimation(item); cancelCurrentAnimationIfExists(item); } @Override public void endAnimations() { super.endAnimations(); for (AnimatorSet animatorSet : likeAnimationsMap.values()) { animatorSet.cancel(); } } public static class FeedItemHolderInfo extends ItemHolderInfo { public String updateAction; public FeedItemHolderInfo(String updateAction) { this.updateAction = updateAction; } } }