Skip to content

Commit

Permalink
Better Dribbble enter/exit transitions.
Browse files Browse the repository at this point in the history
– Remove crufty anim code & replace with shiny transitions
– Better enter animation with a distance staggered slide
– Animate comment entry after loading
  • Loading branch information
nickbutcher committed Sep 1, 2016
1 parent 5f7c75b commit 2048e69
Show file tree
Hide file tree
Showing 17 changed files with 288 additions and 205 deletions.
127 changes: 13 additions & 114 deletions app/src/main/java/io/plaidapp/ui/DribbbleShot.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@

package io.plaidapp.ui;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.app.Activity;
Expand Down Expand Up @@ -51,7 +47,6 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.animation.Interpolator;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
Expand Down Expand Up @@ -94,7 +89,6 @@
import io.plaidapp.util.HtmlUtils;
import io.plaidapp.util.ImeUtils;
import io.plaidapp.util.TransitionUtils;
import io.plaidapp.util.ViewOffsetHelper;
import io.plaidapp.util.ViewUtils;
import io.plaidapp.util.customtabs.CustomTabActivityHelper;
import io.plaidapp.util.glide.CircleTransform;
Expand All @@ -105,7 +99,6 @@
import retrofit2.Response;

import static io.plaidapp.util.AnimUtils.getFastOutSlowInInterpolator;
import static io.plaidapp.util.AnimUtils.getLinearOutSlowInInterpolator;

public class DribbbleShot extends Activity {

Expand Down Expand Up @@ -153,7 +146,6 @@ protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_dribbble_shot);
dribbblePrefs = DribbblePrefs.get(this);
getWindow().getSharedElementReturnTransition().addListener(shotReturnHomeListener);
circleTransform = new CircleTransform(this);
ButterKnife.bind(this);
shotDescription = getLayoutInflater().inflate(R.layout.dribbble_shot_description,
Expand Down Expand Up @@ -285,13 +277,12 @@ private void bindShot(final boolean postponeEnterTransition) {
shotSpacer.setOnClickListener(shotClick);

if (postponeEnterTransition) postponeEnterTransition();
imageView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver
.OnPreDrawListener() {
imageView.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
imageView.getViewTreeObserver().removeOnPreDrawListener(this);
calculateFabPosition();
enterAnimation();
if (postponeEnterTransition) startPostponedEnterTransition();
return true;
}
Expand Down Expand Up @@ -387,6 +378,8 @@ public void onClick(View v) {
shotTimeAgo.setVisibility(View.GONE);
}

commentAnimator = new CommentAnimator();
commentsList.setItemAnimator(commentAnimator);
adapter = new CommentsAdapter(shotDescription, commentFooter, shot.comments_count,
getResources().getInteger(R.integer.comment_expand_collapse_duration));
commentsList.setAdapter(adapter);
Expand All @@ -395,8 +388,6 @@ public void onClick(View v) {
res.getDimensionPixelSize(R.dimen.divider_height),
res.getDimensionPixelSize(R.dimen.keyline_1),
ContextCompat.getColor(this, R.color.divider)));
commentAnimator = new CommentAnimator();
commentsList.setItemAnimator(commentAnimator);
if (shot.comments_count != 0) {
loadComments();
}
Expand Down Expand Up @@ -617,28 +608,6 @@ public void onClick(View view) {
}
};

private Transition.TransitionListener shotReturnHomeListener =
new TransitionUtils.TransitionListenerAdapter() {
@Override
public void onTransitionStart(Transition transition) {
super.onTransitionStart(transition);
// hide the fab as for some reason it jumps position?? TODO work out why
fab.setVisibility(View.INVISIBLE);
// fade out the "toolbar" & list as we don't want them to be visible during return
// animation
back.animate()
.alpha(0f)
.setDuration(100)
.setInterpolator(getLinearOutSlowInInterpolator(DribbbleShot.this));
imageView.setElevation(1f);
back.setElevation(0f);
commentsList.animate()
.alpha(0f)
.setDuration(50)
.setInterpolator(getLinearOutSlowInInterpolator(DribbbleShot.this));
}
};

private void loadComments() {
final Call<List<Comment>> commentsCall =
dribbblePrefs.getApi().getComments(shot.id, 0, DribbbleService.PER_PAGE_MAX);
Expand Down Expand Up @@ -671,82 +640,6 @@ private void calculateFabPosition() {
fab.setMinOffset(imageView.getMinimumHeight() - (fab.getHeight() / 2));
}

/**
* Animate in the title, description and author – can't do this in the window enter transition
* as they get added to the RecyclerView later so do it manually. Also animate the FAB
* translation here so that it plays nicely with #calculateFabPosition
**/
private void enterAnimation() {
Interpolator interp = getFastOutSlowInInterpolator(this);
int offset = title.getHeight();
viewEnterAnimation(title, offset, interp);
if (description.getVisibility() == View.VISIBLE) {
offset *= 1.5f;
viewEnterAnimation(description, offset, interp);
}
offset *= 1.5f;
fabEnterAnimation(interp, offset);
offset *= 1.5f;
viewEnterAnimation(shotActions, offset, interp);
offset *= 1.5f;
viewEnterAnimation(playerName, offset, interp);
viewEnterAnimation(playerAvatar, offset, interp);
viewEnterAnimation(shotTimeAgo, offset, interp);
back.animate()
.alpha(1f)
.setDuration(600L)
.setInterpolator(interp)
.start();
}

private void viewEnterAnimation(View view, float offset, Interpolator interp) {
view.setTranslationY(offset);
view.setAlpha(0.6f);
view.animate()
.translationY(0f)
.alpha(1f)
.setDuration(600L)
.setInterpolator(interp)
.setListener(null)
.start();
}

private void fabEnterAnimation(Interpolator interp, int offset) {
// FAB should enter upwards with content and also scale/fade. As the FAB uses
// translationY to position itself on the title seam, we can animating this property.
// Instead animate the view's layout position (which is a bit more involved).
final ViewOffsetHelper fabOffset = new ViewOffsetHelper(fab);
final View.OnLayoutChangeListener fabLayout = new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int
oldLeft, int oldTop, int oldRight, int oldBottom) {
fabOffset.onViewLayout();
}
};

fab.addOnLayoutChangeListener(fabLayout);
fabOffset.setTopAndBottomOffset(offset);
Animator fabMovement = ObjectAnimator.ofInt(fabOffset, ViewOffsetHelper.OFFSET_Y, 0);
fabMovement.setDuration(600L);
fabMovement.setInterpolator(interp);
fabMovement.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
fab.removeOnLayoutChangeListener(fabLayout);
}
});
fabMovement.start();

Animator showFab = ObjectAnimator.ofPropertyValuesHolder(fab,
PropertyValuesHolder.ofFloat(View.ALPHA, 0f, 1f),
PropertyValuesHolder.ofFloat(View.SCALE_X, 0f, 1f),
PropertyValuesHolder.ofFloat(View.SCALE_Y, 0f, 1f));
showFab.setStartDelay(300L);
showFab.setDuration(300L);
showFab.setInterpolator(getLinearOutSlowInInterpolator(this));
showFab.start();
}

private void doLike() {
performingLike = true;
if (fab.isChecked()) {
Expand Down Expand Up @@ -874,10 +767,10 @@ public void onTransitionEnd(Transition transition) {
}

void addComments(List<Comment> newComments) {
comments.addAll(newComments);
loading = false;
hideLoadingIndicator();
noComments = false;
notifyDataSetChanged();
comments.addAll(newComments);
notifyItemRangeInserted(1, newComments.size());
}

void removeCommentingFooter() {
Expand Down Expand Up @@ -1164,6 +1057,12 @@ private void bindPartialCommentChange(
private Comment getComment(int adapterPosition) {
return comments.get(adapterPosition - 1); // description
}

private void hideLoadingIndicator() {
if (!loading) return;
loading = false;
notifyItemRemoved(1);
}
}

/* package */ static class SimpleViewHolder extends RecyclerView.ViewHolder {
Expand Down
61 changes: 61 additions & 0 deletions app/src/main/java/io/plaidapp/ui/transitions/BackgroundFade.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.plaidapp.ui.transitions;

import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.support.annotation.Keep;
import android.transition.TransitionValues;
import android.transition.Visibility;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

import io.plaidapp.util.ViewUtils;

/**
* A transition which fades in/out the background {@link Drawable} of a View.
*/
public class BackgroundFade extends Visibility {

public BackgroundFade() {
super();
}

@Keep
public BackgroundFade(Context context, AttributeSet attrs) {
super(context, attrs);
}

@Override
public Animator onAppear(ViewGroup sceneRoot, View view,
TransitionValues startValues, TransitionValues endValues) {
if (view == null || view.getBackground() == null) return null;
Drawable background = view.getBackground();
background.setAlpha(0);
return ObjectAnimator.ofInt(background, ViewUtils.DRAWABLE_ALPHA, 0, 255);
}

@Override
public Animator onDisappear(ViewGroup sceneRoot, View view,
TransitionValues startValues, TransitionValues endValues) {
if (view == null || view.getBackground() == null) return null;
return ObjectAnimator.ofInt(view.getBackground(), ViewUtils.DRAWABLE_ALPHA, 0);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,17 +50,17 @@ public void captureEndValues(TransitionValues transitionValues) {
// as we're going to remove the offset (which drives the parallax) we need to
// compensate for this by adjusting the target bounds.
Rect bounds = (Rect) transitionValues.values.get(PROPNAME_BOUNDS);
bounds.offset(0, -psv.getOffset());
bounds.offset(0, psv.getOffset());
transitionValues.values.put(PROPNAME_BOUNDS, bounds);
}

@Override
public Animator createAnimator(ViewGroup sceneRoot,
TransitionValues startValues,
TransitionValues endValues) {
if (startValues == null || endValues == null
|| !(endValues.view instanceof ParallaxScrimageView)) return null;
Animator changeBounds = super.createAnimator(sceneRoot, startValues, endValues);
if (startValues == null || endValues == null
|| !(endValues.view instanceof ParallaxScrimageView)) return changeBounds;
ParallaxScrimageView psv = ((ParallaxScrimageView) endValues.view);
if (psv.getOffset() == 0) return changeBounds;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,21 @@
package io.plaidapp.ui.transitions;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.support.annotation.Keep;
import android.transition.TransitionValues;
import android.transition.Visibility;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

import java.util.List;

import io.plaidapp.R;
import io.plaidapp.util.TransitionUtils;

/**
* An alternative to {@link android.transition.Slide} which staggers elements by <b>distance</b>
Expand All @@ -43,8 +48,11 @@ public class StaggeredDistanceSlide extends Visibility {

private int spread = 1;

public StaggeredDistanceSlide() { }
public StaggeredDistanceSlide() {
super();
}

@Keep
public StaggeredDistanceSlide(Context context, AttributeSet attrs) {
super(context, attrs);
final TypedArray a =
Expand All @@ -65,15 +73,27 @@ public void setSpread(int spread) {
public Animator onAppear(ViewGroup sceneRoot, View view,
TransitionValues startValues, TransitionValues endValues) {
int[] position = (int[]) endValues.values.get(PROPNAME_SCREEN_LOCATION);
view.setTranslationY(sceneRoot.getHeight() + (position[1] * spread));
return ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, 0f);
return createAnimator(view, sceneRoot.getHeight() + (position[1] * spread), 0f);
}

@Override
public Animator onDisappear(ViewGroup sceneRoot, View view,
TransitionValues startValues, TransitionValues endValues) {
int[] position = (int[]) endValues.values.get(PROPNAME_SCREEN_LOCATION);
return ObjectAnimator.ofFloat(view, View.TRANSLATION_Y,
sceneRoot.getHeight() + (position[1] * spread));
return createAnimator(view, 0f, sceneRoot.getHeight() + (position[1] * spread));
}

private Animator createAnimator(
final View view, float startTranslationY, float endTranslationY) {
view.setTranslationY(startTranslationY);
final List<Boolean> ancestralClipping = TransitionUtils.setAncestralClipping(view, false);
Animator transition = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, endTranslationY);
transition.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
TransitionUtils.restoreAncestralClipping(view, ancestralClipping);
}
});
return transition;
}
}
Loading

0 comments on commit 2048e69

Please sign in to comment.