diff --git a/mobile/android/base/animation/BounceAnimator.java b/mobile/android/base/animation/BounceAnimator.java new file mode 100644 index 00000000000..274f21a37b6 --- /dev/null +++ b/mobile/android/base/animation/BounceAnimator.java @@ -0,0 +1,55 @@ +package org.mozilla.gecko.animation; + +import java.util.LinkedList; +import java.util.List; + +import android.view.View; +import android.view.animation.AccelerateInterpolator; + +import com.nineoldandroids.animation.Animator; +import com.nineoldandroids.animation.AnimatorSet; +import com.nineoldandroids.animation.ObjectAnimator; +import com.nineoldandroids.animation.ValueAnimator; + +/** + * This is an Animator that chains AccelerateInterpolators. It can be used to create a customized + * Bounce animation. + * + * After constructing an instance, animations can be queued up sequentially with the + * {@link #queue(Attributes) queue} method. + */ +public class BounceAnimator extends ValueAnimator { + + public static final class Attributes { + public final float value; + public final int durationMs; + + public Attributes(float value, int duration) { + this.value = value; + this.durationMs = duration; + } + } + + private final View mView; + private final String mPropertyName; + private List animatorChain = new LinkedList(); + + public BounceAnimator(View view, String property) { + mView = view; + mPropertyName = property; + } + + public void queue(Attributes attrs) { + final ValueAnimator animator = ObjectAnimator.ofFloat(mView, mPropertyName, attrs.value); + animator.setDuration(attrs.durationMs); + animator.setInterpolator(new AccelerateInterpolator()); + animatorChain.add(animator); + } + + @Override + public void start() { + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playSequentially(animatorChain); + animatorSet.start(); + } +} diff --git a/mobile/android/base/home/HomePagerTabStrip.java b/mobile/android/base/home/HomePagerTabStrip.java index cd1755cc3bd..9d87607094b 100644 --- a/mobile/android/base/home/HomePagerTabStrip.java +++ b/mobile/android/base/home/HomePagerTabStrip.java @@ -5,12 +5,21 @@ package org.mozilla.gecko.home; +import org.mozilla.gecko.R; +import org.mozilla.gecko.animation.BounceAnimator; +import org.mozilla.gecko.animation.BounceAnimator.Attributes; + import android.content.Context; import android.content.res.TypedArray; import android.support.v4.view.PagerTabStrip; import android.util.AttributeSet; +import android.view.View; +import android.view.ViewTreeObserver; -import org.mozilla.gecko.R; +import com.nineoldandroids.animation.AnimatorSet; +import com.nineoldandroids.animation.ObjectAnimator; +import com.nineoldandroids.animation.ValueAnimator; +import com.nineoldandroids.view.ViewHelper; /** * HomePagerTabStrip is a custom implementation of PagerTabStrip @@ -19,6 +28,15 @@ import org.mozilla.gecko.R; class HomePagerTabStrip extends PagerTabStrip { + private static final String LOGTAG = "PagerTabStrip"; + private static final int ANIMATION_DELAY_MS = 250; + private static final int ALPHA_MS = 10; + private static final int BOUNCE1_MS = 350; + private static final int BOUNCE2_MS = 200; + private static final int BOUNCE3_MS = 100; + private static final int BOUNCE4_MS = 100; + private static final int INIT_OFFSET = 100; + public HomePagerTabStrip(Context context) { super(context); } @@ -31,5 +49,60 @@ class HomePagerTabStrip extends PagerTabStrip { a.recycle(); setTabIndicatorColor(color); + + getViewTreeObserver().addOnPreDrawListener(new PreDrawListener()); + } + + private void animateTitles() { + final View prevTextView = getChildAt(0); + final View nextTextView = getChildAt(getChildCount() - 1); + + if (prevTextView == null || nextTextView == null) { + return; + } + + // Set up initial values for the views that will be animated. + ViewHelper.setTranslationX(prevTextView, -INIT_OFFSET); + ViewHelper.setAlpha(prevTextView, 0); + ViewHelper.setTranslationX(nextTextView, INIT_OFFSET); + ViewHelper.setAlpha(nextTextView, 0); + + // Alpha animations. + final ValueAnimator alpha1 = ObjectAnimator.ofFloat(prevTextView, "alpha", 1); + final ValueAnimator alpha2 = ObjectAnimator.ofFloat(nextTextView, "alpha", 1); + + final AnimatorSet alphaAnimatorSet = new AnimatorSet(); + alphaAnimatorSet.playTogether(alpha1, alpha2); + alphaAnimatorSet.setStartDelay(ANIMATION_DELAY_MS); + alphaAnimatorSet.setDuration(ALPHA_MS); + + // Bounce animation. + final float bounceDistance = getWidth()/100f; // Hack: TextFields still have 0 width here. + + final BounceAnimator prevBounceAnimator = new BounceAnimator(prevTextView, "translationX"); + prevBounceAnimator.queue(new Attributes(bounceDistance, BOUNCE1_MS)); + prevBounceAnimator.queue(new Attributes(-bounceDistance/4, BOUNCE2_MS)); + prevBounceAnimator.queue(new Attributes(0, BOUNCE4_MS)); + prevBounceAnimator.setStartDelay(ANIMATION_DELAY_MS); + + final BounceAnimator nextBounceAnimator = new BounceAnimator(nextTextView, "translationX"); + nextBounceAnimator.queue(new Attributes(-bounceDistance, BOUNCE1_MS)); + nextBounceAnimator.queue(new Attributes(bounceDistance/4, BOUNCE2_MS)); + nextBounceAnimator.queue(new Attributes(0, BOUNCE4_MS)); + nextBounceAnimator.setStartDelay(ANIMATION_DELAY_MS); + + // Start animations. + alphaAnimatorSet.start(); + prevBounceAnimator.start(); + nextBounceAnimator.start(); + } + + private class PreDrawListener implements ViewTreeObserver.OnPreDrawListener { + @Override + public boolean onPreDraw() { + animateTitles(); + getViewTreeObserver().removeOnPreDrawListener(this); + return true; + } } } diff --git a/mobile/android/base/moz.build b/mobile/android/base/moz.build index 04c80657800..32c9cb21183 100644 --- a/mobile/android/base/moz.build +++ b/mobile/android/base/moz.build @@ -125,6 +125,7 @@ gbjar.sources += [ 'AndroidGamepadManager.java', 'animation/AnimationUtils.java', 'animation/AnimatorProxy.java', + 'animation/BounceAnimator.java', 'animation/HeightChangeAnimation.java', 'animation/PropertyAnimator.java', 'animation/Rotate3DAnimation.java',