Skip to content

Commit

Permalink
Made DN story icons fade in when returning to grid.
Browse files Browse the repository at this point in the history
– Also fixed up the HomeGridItemAnimator some.
  • Loading branch information
nickbutcher committed Aug 25, 2016
1 parent d1a27df commit ed27d13
Show file tree
Hide file tree
Showing 2 changed files with 214 additions and 93 deletions.
22 changes: 17 additions & 5 deletions app/src/main/java/io/plaidapp/ui/FeedAdapter.java
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,18 @@ public void onClick(View commentsView) {
(Story) getItem(holder.getAdapterPosition()));
ReflowText.addExtras(intent, new ReflowText.ReflowableTextView(holder.title));
setGridItemContentTransitions(holder.itemView);

// on return, fade the pocket & comments buttons in
host.setExitSharedElementCallback(new SharedElementCallback() {
@Override
public void onSharedElementStart(List<String> sharedElementNames, List<View>
sharedElements, List<View> sharedElementSnapshots) {
host.setExitSharedElementCallback(null);
notifyItemChanged(holder.getAdapterPosition(),
HomeGridItemAnimator.STORY_COMMENTS_RETURN);
}
});

final ActivityOptions options =
ActivityOptions.makeSceneTransitionAnimation(host,
Pair.create((View) holder.title,
Expand All @@ -241,7 +253,7 @@ public void onClick(final View view) {
((Story) getItem(holder.getAdapterPosition())).url);
// notify changed with a payload asking RV to run the anim
notifyItemChanged(holder.getAdapterPosition(),
HomeGridItemAnimator.ANIMATE_ADD_POCKET);
HomeGridItemAnimator.ADD_TO_POCKET);
}
});
}
Expand Down Expand Up @@ -689,7 +701,7 @@ public void onMapSharedElements(List<String> names, Map<String, View> sharedElem

BadgedFourThreeImageView image;

public DribbbleShotHolder(View itemView) {
DribbbleShotHolder(View itemView) {
super(itemView);
image = (BadgedFourThreeImageView) itemView;
}
Expand All @@ -702,7 +714,7 @@ public DribbbleShotHolder(View itemView) {
@BindView(R.id.story_comments) TextView comments;
@BindView(R.id.pocket) ImageButton pocket;

public DesignerNewsStoryHolder(View itemView, boolean pocketIsInstalled) {
DesignerNewsStoryHolder(View itemView, boolean pocketIsInstalled) {
super(itemView);
ButterKnife.bind(this, itemView);
pocket.setVisibility(pocketIsInstalled ? View.VISIBLE : View.GONE);
Expand All @@ -715,7 +727,7 @@ public DesignerNewsStoryHolder(View itemView, boolean pocketIsInstalled) {
@BindView(R.id.tagline) TextView tagline;
@BindView(R.id.story_comments) TextView comments;

public ProductHuntStoryHolder(View itemView) {
ProductHuntStoryHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
Expand All @@ -725,7 +737,7 @@ public ProductHuntStoryHolder(View itemView) {

ProgressBar progress;

public LoadingMoreHolder(View itemView) {
LoadingMoreHolder(View itemView) {
super(itemView);
progress = (ProgressBar) itemView;
}
Expand Down
285 changes: 197 additions & 88 deletions app/src/main/java/io/plaidapp/ui/HomeGridItemAnimator.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.support.annotation.NonNull;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.RecyclerView;
import android.util.Pair;
import android.view.View;
import android.view.ViewGroup;

Expand All @@ -35,28 +35,44 @@
import io.plaidapp.util.ViewUtils;

/**
* An extension to {@link DefaultItemAnimator} for running animations specific to our home grid.
* A {@link RecyclerView.ItemAnimator} for running animations specific to our home grid.
*/
public class HomeGridItemAnimator extends SlideInItemAnimator {

public static final int ANIMATE_ADD_POCKET = 7;
// Constant payloads, for use with Adapter#notifyItemChanged
public static final int ADD_TO_POCKET = 0x1;
public static final int STORY_COMMENTS_RETURN = 0x2;

// Pending animations
private FeedAdapter.DesignerNewsStoryHolder pendingAddToPocket;
private FeedAdapter.DesignerNewsStoryHolder pendingStoryCommentsReturn;

// Currently running animations
private Pair<FeedAdapter.DesignerNewsStoryHolder, AnimatorSet> runningAddToPocket;
private Pair<FeedAdapter.DesignerNewsStoryHolder, AnimatorSet> runningStoryCommentsReturn;

@Override
public boolean canReuseUpdatedViewHolder(RecyclerView.ViewHolder viewHolder) {
return true;
}

@Override
public ItemHolderInfo obtainHolderInfo() {
return new HomeGridItemHolderInfo();
}

@NonNull
@Override
public ItemHolderInfo recordPreLayoutInformation(RecyclerView.State state,
RecyclerView.ViewHolder viewHolder,
int changeFlags,
List<Object> payloads) {
ItemHolderInfo info = super.recordPreLayoutInformation(state, viewHolder, changeFlags,
payloads);
if (payloads.contains(ANIMATE_ADD_POCKET)) {
DesignerNewsItemHolderInfo dnInfo = (DesignerNewsItemHolderInfo) info;
dnInfo.animateAddToPocket = true;
ItemHolderInfo info =
super.recordPreLayoutInformation(state, viewHolder, changeFlags, payloads);
if (info instanceof HomeGridItemHolderInfo) {
HomeGridItemHolderInfo dnInfo = (HomeGridItemHolderInfo) info;
dnInfo.animateAddToPocket = payloads.contains(ADD_TO_POCKET);
dnInfo.returnFromComments = payloads.contains(STORY_COMMENTS_RETURN);
return dnInfo;
}
return info;
Expand All @@ -67,92 +83,185 @@ public boolean animateChange(RecyclerView.ViewHolder oldHolder,
RecyclerView.ViewHolder newHolder,
ItemHolderInfo preInfo,
ItemHolderInfo postInfo) {
if (preInfo instanceof DesignerNewsItemHolderInfo
&& ((DesignerNewsItemHolderInfo) preInfo).animateAddToPocket) {
final FeedAdapter.DesignerNewsStoryHolder holder =
(FeedAdapter.DesignerNewsStoryHolder) newHolder;

// setup for anim
((ViewGroup) holder.pocket.getParent().getParent()).setClipChildren(false);
final int initialLeft = holder.pocket.getLeft();
final int initialTop = holder.pocket.getTop();
final int translatedLeft =
(holder.itemView.getWidth() - holder.pocket.getWidth()) / 2;
final int translatedTop =
initialTop - ((holder.itemView.getHeight() - holder.pocket.getHeight()) / 2);
final GravityArcMotion arc = new GravityArcMotion();

// animate the title & pocket icon up, scale the pocket icon up
Animator titleMoveFadeOut = ObjectAnimator.ofPropertyValuesHolder(holder.title,
PropertyValuesHolder.ofFloat(View .TRANSLATION_Y,
-(holder.itemView.getHeight() / 5)),
PropertyValuesHolder.ofFloat(View.ALPHA, 0.54f));

Animator pocketMoveUp = ObjectAnimator.ofFloat(holder.pocket,
View.TRANSLATION_X, View.TRANSLATION_Y,
arc.getPath(initialLeft, initialTop, translatedLeft, translatedTop));
Animator pocketScaleUp = ObjectAnimator.ofPropertyValuesHolder(holder.pocket,
PropertyValuesHolder.ofFloat(View.SCALE_X, 3f),
PropertyValuesHolder.ofFloat(View.SCALE_Y, 3f));
ObjectAnimator pocketFadeUp = ObjectAnimator.ofInt(holder.pocket,
ViewUtils.IMAGE_ALPHA, 255);

AnimatorSet up = new AnimatorSet();
up.playTogether(titleMoveFadeOut, pocketMoveUp, pocketScaleUp, pocketFadeUp);
up.setDuration(300);
up.setInterpolator(AnimUtils.getFastOutSlowInInterpolator(holder.itemView.getContext()));

// animate everything back into place
Animator titleMoveFadeIn = ObjectAnimator.ofPropertyValuesHolder(holder.title,
PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, 0f),
PropertyValuesHolder.ofFloat(View.ALPHA, 1f));
Animator pocketMoveDown = ObjectAnimator.ofFloat(holder.pocket,
View.TRANSLATION_X, View.TRANSLATION_Y,
arc.getPath(translatedLeft, translatedTop, 0, 0));
Animator pvhPocketScaleDown = ObjectAnimator.ofPropertyValuesHolder(holder.pocket,
PropertyValuesHolder.ofFloat(View.SCALE_X, 1f),
PropertyValuesHolder.ofFloat(View.SCALE_Y, 1f));
ObjectAnimator pocketFadeDown = ObjectAnimator.ofInt(holder.pocket,
ViewUtils.IMAGE_ALPHA, 138);

AnimatorSet down = new AnimatorSet();
down.playTogether(titleMoveFadeIn, pocketMoveDown, pvhPocketScaleDown, pocketFadeDown);
down.setDuration(300);
down.setInterpolator(AnimUtils.getFastOutSlowInInterpolator(holder.itemView
.getContext()));
down.setStartDelay(500);

// play it
AnimatorSet upDown = new AnimatorSet();
upDown.playSequentially(up, down);

// clean up
upDown.addListener(new AnimatorListenerAdapter() {

@Override
public void onAnimationStart(Animator animation) {
holder.itemView.setHasTransientState(true);
dispatchAnimationStarted(holder);
}

@Override
public void onAnimationEnd(Animator animation) {
((ViewGroup) holder.pocket.getParent().getParent()).setClipChildren(true);
holder.itemView.setHasTransientState(false);
dispatchAnimationFinished(holder);
}
});
upDown.start();
boolean runPending = super.animateChange(oldHolder, newHolder, preInfo, postInfo);

if (preInfo instanceof HomeGridItemHolderInfo) {
HomeGridItemHolderInfo info = (HomeGridItemHolderInfo) preInfo;
if (info.animateAddToPocket) {
pendingAddToPocket = (FeedAdapter.DesignerNewsStoryHolder) newHolder;
runPending = true;
}
if (info.returnFromComments) {
pendingStoryCommentsReturn = (FeedAdapter.DesignerNewsStoryHolder) newHolder;
runPending = true;
}
}
return super.animateChange(oldHolder, newHolder, preInfo, postInfo);
return runPending;
}

@Override
public ItemHolderInfo obtainHolderInfo() {
return new DesignerNewsItemHolderInfo();
public void runPendingAnimations() {
super.runPendingAnimations();
if (pendingAddToPocket != null) {
animateAddToPocket(pendingAddToPocket);
pendingAddToPocket = null;
}
if (pendingStoryCommentsReturn != null) {
animateStoryCommentReturn(pendingStoryCommentsReturn);
pendingStoryCommentsReturn = null;
}
}

/* package */ class DesignerNewsItemHolderInfo extends ItemHolderInfo {
@Override
public void endAnimation(RecyclerView.ViewHolder holder) {
super.endAnimation(holder);
if (runningAddToPocket != null && runningAddToPocket.first == holder) {
runningAddToPocket.second.cancel();
}
if (runningStoryCommentsReturn != null && runningStoryCommentsReturn.first == holder) {
runningStoryCommentsReturn.second.cancel();
}
}

@Override
public void endAnimations() {
super.endAnimations();
if (runningAddToPocket != null) {
runningAddToPocket.second.cancel();
}
if (runningStoryCommentsReturn != null) {
runningStoryCommentsReturn.second.cancel();
}
}

@Override
public boolean isRunning() {
return super.isRunning()
|| (runningAddToPocket != null && runningAddToPocket.second.isRunning())
|| (runningStoryCommentsReturn != null
&& runningStoryCommentsReturn.second.isRunning());
}

private void animateAddToPocket(final FeedAdapter.DesignerNewsStoryHolder holder) {
endAnimation(holder);

// setup for anim
((ViewGroup) holder.pocket.getParent().getParent()).setClipChildren(false);
final int initialLeft = holder.pocket.getLeft();
final int initialTop = holder.pocket.getTop();
final int translatedLeft =
(holder.itemView.getWidth() - holder.pocket.getWidth()) / 2;
final int translatedTop =
initialTop - ((holder.itemView.getHeight() - holder.pocket.getHeight()) / 2);
final GravityArcMotion arc = new GravityArcMotion();

// animate the title & pocket icon up, scale the pocket icon up
Animator titleMoveFadeOut = ObjectAnimator.ofPropertyValuesHolder(holder.title,
PropertyValuesHolder.ofFloat(View.TRANSLATION_Y,
-(holder.itemView.getHeight() / 5)),
PropertyValuesHolder.ofFloat(View.ALPHA, 0.54f));

Animator pocketMoveUp = ObjectAnimator.ofFloat(holder.pocket,
View.TRANSLATION_X, View.TRANSLATION_Y,
arc.getPath(initialLeft, initialTop, translatedLeft, translatedTop));
Animator pocketScaleUp = ObjectAnimator.ofPropertyValuesHolder(holder.pocket,
PropertyValuesHolder.ofFloat(View.SCALE_X, 3f),
PropertyValuesHolder.ofFloat(View.SCALE_Y, 3f));
ObjectAnimator pocketFadeUp = ObjectAnimator.ofInt(holder.pocket,
ViewUtils.IMAGE_ALPHA, 255);

AnimatorSet up = new AnimatorSet();
up.playTogether(titleMoveFadeOut, pocketMoveUp, pocketScaleUp, pocketFadeUp);
up.setDuration(300L);
up.setInterpolator(AnimUtils.getFastOutSlowInInterpolator(holder.itemView.getContext()));

// animate everything back into place
Animator titleMoveFadeIn = ObjectAnimator.ofPropertyValuesHolder(holder.title,
PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, 0f),
PropertyValuesHolder.ofFloat(View.ALPHA, 1f));
Animator pocketMoveDown = ObjectAnimator.ofFloat(holder.pocket,
View.TRANSLATION_X, View.TRANSLATION_Y,
arc.getPath(translatedLeft, translatedTop, 0, 0));
Animator pvhPocketScaleDown = ObjectAnimator.ofPropertyValuesHolder(holder.pocket,
PropertyValuesHolder.ofFloat(View.SCALE_X, 1f),
PropertyValuesHolder.ofFloat(View.SCALE_Y, 1f));
ObjectAnimator pocketFadeDown = ObjectAnimator.ofInt(holder.pocket,
ViewUtils.IMAGE_ALPHA, 178);

AnimatorSet down = new AnimatorSet();
down.playTogether(titleMoveFadeIn, pocketMoveDown, pvhPocketScaleDown, pocketFadeDown);
down.setStartDelay(500L);
down.setDuration(300L);
down.setInterpolator(AnimUtils.getFastOutSlowInInterpolator(holder.itemView.getContext()));

AnimatorSet addToPocketAnim = new AnimatorSet();
addToPocketAnim.playSequentially(up, down);

addToPocketAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
dispatchChangeStarting(holder, false);
}

@Override
public void onAnimationEnd(Animator animation) {
((ViewGroup) holder.pocket.getParent().getParent()).setClipChildren(true);
runningAddToPocket = null;
dispatchChangeFinished(holder, false);
}

@Override
public void onAnimationCancel(Animator animation) {
holder.title.setAlpha(1f);
holder.title.setTranslationY(0f);
holder.pocket.setTranslationX(0f);
holder.pocket.setTranslationY(0f);
holder.pocket.setScaleX(1f);
holder.pocket.setScaleY(1f);
holder.pocket.setImageAlpha(178);
runningAddToPocket = null;
}
});
runningAddToPocket = Pair.create(holder, addToPocketAnim);
addToPocketAnim.start();
}

private void animateStoryCommentReturn(final FeedAdapter.DesignerNewsStoryHolder holder) {
endAnimation(holder);

AnimatorSet commentsReturnAnim = new AnimatorSet();
commentsReturnAnim.playTogether(
ObjectAnimator.ofFloat(holder.pocket, View.ALPHA, 0f, 1f),
ObjectAnimator.ofFloat(holder.comments, View.ALPHA, 0f, 1f));
commentsReturnAnim.setDuration(120L);
commentsReturnAnim.setInterpolator(
AnimUtils.getLinearOutSlowInInterpolator(holder.itemView.getContext()));
commentsReturnAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
dispatchChangeStarting(holder, false);
}

@Override
public void onAnimationEnd(Animator animation) {
runningStoryCommentsReturn = null;
dispatchChangeFinished(holder, false);
}

@Override
public void onAnimationCancel(Animator animation) {
holder.pocket.setAlpha(1f);
holder.comments.setAlpha(1f);
runningStoryCommentsReturn = null;
}
});
runningStoryCommentsReturn = Pair.create(holder, commentsReturnAnim);
commentsReturnAnim.start();
}

private class HomeGridItemHolderInfo extends ItemHolderInfo {
boolean animateAddToPocket;
boolean returnFromComments;
}

}

0 comments on commit ed27d13

Please sign in to comment.