// Copyright 2013 Google Inc. All Rights Reserved. package com.android.deskclock.widget.sgv; import android.R.interpolator; import android.animation.Animator; import android.animation.Animator.AnimatorListener; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.Point; import android.view.View; import android.view.WindowManager; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import java.util.List; public class SgvAnimationHelper { /** * Supported entrance animations for views in the {@link StaggeredGridView}. */ public enum AnimationIn { NONE, // Fly in all views from the bottom of the screen FLY_UP_ALL_VIEWS, // New views expand into view from height 0. Existing views are updated and translated // to their new positions if appropriate. EXPAND_NEW_VIEWS, // New views expand into view from height 0. Existing views are updated and translated // to their new positions if appropriate. This animation is done for all views // simultaneously without a cascade effect. EXPAND_NEW_VIEWS_NO_CASCADE, // New views are flown in from the bottom. Existing views are updated and translated // to their new positions if appropriate. FLY_IN_NEW_VIEWS, // New views are slid in from the side. Existing views are updated and translated // to their new positions if appropriate. SLIDE_IN_NEW_VIEWS, // Fade in all new views FADE, } /** * Supported exit animations for views in the {@link StaggeredGridView}. */ public enum AnimationOut { NONE, // Stale views are faded out of view. Existing views are then updated and translated // to their new positions if appropriate. FADE, // Stale views are dropped to the bottom of the screen. Existing views are then updated // and translated to their new positions if appropriate. FLY_DOWN, // Stale views are slid to the side of the screen. Existing views are then updated // and translated to their new positions if appropriate. SLIDE, // Stale views are collapsed to height 0. Existing views are then updated // and translated to their new positions if appropriate. COLLAPSE } private static Interpolator sDecelerateQuintInterpolator; private static final int ANIMATION_LONG_SCREEN_SIZE = 1600; private static final int ANIMATION_MED_SCREEN_SIZE = 1200; // Duration of an individual animation when the children of the grid are laid out again. These // are measured in milliseconds and based on the height of the screen. private static final int ANIMATION_SHORT_DURATION = 400; private static final int ANIMATION_MED_DURATION = 450; private static final int ANIMATION_LONG_DURATION = 500; public static final float ANIMATION_ROTATION_DEGREES = 25.0f; /** * Duration of an individual animation when the children of the grid are laid out again. * This is measured in milliseconds. */ private static int sAnimationDuration = ANIMATION_MED_DURATION; public static void initialize(Context context) { sDecelerateQuintInterpolator = AnimationUtils.loadInterpolator(context, interpolator.decelerate_quint); final Point size = new Point(); ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)). getDefaultDisplay().getSize(size); final int screenHeight = size.y; if (screenHeight >= ANIMATION_LONG_SCREEN_SIZE) { sAnimationDuration = ANIMATION_LONG_DURATION; } else if (screenHeight >= ANIMATION_MED_SCREEN_SIZE) { sAnimationDuration = ANIMATION_MED_DURATION; } else { sAnimationDuration = ANIMATION_SHORT_DURATION; } } public static int getDefaultAnimationDuration() { return sAnimationDuration; } public static Interpolator getDefaultAnimationInterpolator() { return sDecelerateQuintInterpolator; } /** * Add animations to translate a view's X-translation. {@link AnimatorListener} can be null. */ private static void addXTranslationAnimators(List animators, final View view, int startTranslation, final int endTranslation, int animationDelay, AnimatorListener listener) { // We used to skip the animation if startTranslation == endTranslation, // but to add a recycle view listener, we need at least one animation view.setTranslationX(startTranslation); final ObjectAnimator translateAnimatorX = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, startTranslation, endTranslation); translateAnimatorX.setInterpolator(sDecelerateQuintInterpolator); translateAnimatorX.setDuration(sAnimationDuration); translateAnimatorX.setStartDelay(animationDelay); if (listener != null) { translateAnimatorX.addListener(listener); } animators.add(translateAnimatorX); } /** * Add animations to translate a view's Y-translation. {@link AnimatorListener} can be null. */ private static void addYTranslationAnimators(List animators, final View view, int startTranslation, final int endTranslation, int animationDelay, AnimatorListener listener) { // We used to skip the animation if startTranslation == endTranslation, // but to add a recycle view listener, we need at least one animation view.setTranslationY(startTranslation); final ObjectAnimator translateAnimatorY = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, startTranslation, endTranslation); translateAnimatorY.setInterpolator(sDecelerateQuintInterpolator); translateAnimatorY.setDuration(sAnimationDuration); translateAnimatorY.setStartDelay(animationDelay); if (listener != null) { translateAnimatorY.addListener(listener); } animators.add(translateAnimatorY); } /** * Translate a view to the specified translation values, and animate the translations to 0. */ public static void addXYTranslationAnimators(List animators, final View view, int xTranslation, int yTranslation, int animationDelay) { addXTranslationAnimators(animators, view, xTranslation, 0, animationDelay, null); addYTranslationAnimators(animators, view, yTranslation, 0, animationDelay, null); } /** * Translate a view to the specified translation values, while rotating to the specified * rotation value. */ public static void addTranslationRotationAnimators(List animators, final View view, int xTranslation, int yTranslation, float rotation, int animationDelay) { addXYTranslationAnimators(animators, view, xTranslation, yTranslation, animationDelay); view.setLayerType(View.LAYER_TYPE_HARDWARE, null); view.setRotation(rotation); final ObjectAnimator rotateAnimatorY = ObjectAnimator.ofFloat(view, View.ROTATION, view.getRotation(), 0.0f); rotateAnimatorY.setInterpolator(sDecelerateQuintInterpolator); rotateAnimatorY.setDuration(sAnimationDuration); rotateAnimatorY.setStartDelay(animationDelay); rotateAnimatorY.addListener(new AnimatorListenerAdapter() { private boolean mIsCanceled = false; @Override public void onAnimationCancel(Animator animation) { mIsCanceled = true; } @Override public void onAnimationEnd(Animator animation) { if (!mIsCanceled) { view.setRotation(0); } view.setLayerType(View.LAYER_TYPE_NONE, null); } }); animators.add(rotateAnimatorY); } /** * Expand a view into view by scaling up vertically from 0. */ public static void addExpandInAnimators(List animators, final View view, int animationDelay) { view.setLayerType(View.LAYER_TYPE_HARDWARE, null); view.setScaleY(0); final ObjectAnimator scaleAnimatorY = ObjectAnimator.ofFloat(view, View.SCALE_Y, view.getScaleY(), 1.0f); scaleAnimatorY.setInterpolator(sDecelerateQuintInterpolator); scaleAnimatorY.setDuration(sAnimationDuration); scaleAnimatorY.setStartDelay(animationDelay); scaleAnimatorY.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { view.setScaleY(1.0f); view.setLayerType(View.LAYER_TYPE_NONE, null); } }); animators.add(scaleAnimatorY); } /** * Collapse a view out by scaling it from its current scaled value to 0. */ public static void addCollapseOutAnimators(List animators, final View view, int animationDelay) { view.setLayerType(View.LAYER_TYPE_HARDWARE, null); final ObjectAnimator scaleAnimatorY = ObjectAnimator.ofFloat(view, View.SCALE_Y, view.getScaleY(), 0); scaleAnimatorY.setInterpolator(sDecelerateQuintInterpolator); scaleAnimatorY.setDuration(sAnimationDuration); scaleAnimatorY.setStartDelay(animationDelay); scaleAnimatorY.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { view.setScaleY(0); view.setLayerType(View.LAYER_TYPE_NONE, null); } }); animators.add(scaleAnimatorY); } /** * Collapse a view out by scaling it from its current scaled value to 0. * The animators are expected to run immediately without a start delay. */ public static void addCollapseOutAnimators(List animators, final View view) { addCollapseOutAnimators(animators, view, 0 /* animation delay */); } /** * Fly a view out by moving it vertically off the bottom of the screen. */ public static void addFlyOutAnimators(List animators, final View view, int startTranslation, int endTranslation, int animationDelay) { addYTranslationAnimators(animators, view, startTranslation, endTranslation, animationDelay, null); } public static void addFlyOutAnimators(List animators, final View view, int startTranslation, int endTranslation) { addFlyOutAnimators(animators, view, startTranslation, endTranslation, 0 /* animation delay */); } public static void addSlideInFromRightAnimators(List animators, final View view, int startTranslation, int animationDelay) { addXTranslationAnimators(animators, view, startTranslation, 0, animationDelay, null); addFadeAnimators(animators, view, 0, 1.0f, animationDelay); } /** * Slide a view out of view from the start to end position, fading the view out as it * approaches the end position. */ public static void addSlideOutAnimators(List animators, final View view, int startTranslation, int endTranslation, int animationDelay) { addFadeAnimators(animators, view, view.getAlpha(), 0, animationDelay); addXTranslationAnimators(animators, view, startTranslation, endTranslation, animationDelay, null); } /** * Slide a view out of view from the start to end position, fading the view out as it * approaches the end position. The animators are expected to run immediately without a * start delay. */ public static void addSlideOutAnimators(List animators, final View view, int startTranslation, int endTranslation) { addSlideOutAnimators(animators, view, startTranslation, endTranslation, 0 /* animation delay */); } /** * Add animations to fade a view from the specified start alpha value to end value. */ public static void addFadeAnimators(List animators, final View view, float startAlpha, final float endAlpha, int animationDelay) { if (startAlpha == endAlpha) { return; } view.setLayerType(View.LAYER_TYPE_HARDWARE, null); view.setAlpha(startAlpha); final ObjectAnimator fadeAnimator = ObjectAnimator.ofFloat(view, View.ALPHA, view.getAlpha(), endAlpha); fadeAnimator.setInterpolator(sDecelerateQuintInterpolator); fadeAnimator.setDuration(sAnimationDuration); fadeAnimator.setStartDelay(animationDelay); fadeAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { view.setAlpha(endAlpha); view.setLayerType(View.LAYER_TYPE_NONE, null); } }); animators.add(fadeAnimator); } /** * Add animations to fade a view from the specified start alpha value to end value. * The animators are expected to run immediately without a start delay. */ public static void addFadeAnimators(List animators, final View view, float startAlpha, final float endAlpha) { addFadeAnimators(animators, view, startAlpha, endAlpha, 0 /* animation delay */); } }