1// Copyright 2013 Google Inc. All Rights Reserved.
2
3package com.android.deskclock.widget.sgv;
4
5import android.R.interpolator;
6import android.animation.Animator;
7import android.animation.Animator.AnimatorListener;
8import android.animation.AnimatorListenerAdapter;
9import android.animation.ObjectAnimator;
10import android.content.Context;
11import android.graphics.Point;
12import android.view.View;
13import android.view.WindowManager;
14import android.view.animation.AnimationUtils;
15import android.view.animation.Interpolator;
16
17
18import java.util.List;
19
20public class SgvAnimationHelper {
21
22    /**
23     * Supported entrance animations for views in the {@link StaggeredGridView}.
24     */
25    public enum AnimationIn {
26        NONE,
27        // Fly in all views from the bottom of the screen
28        FLY_UP_ALL_VIEWS,
29        // New views expand into view from height 0.  Existing views are updated and translated
30        // to their new positions if appropriate.
31        EXPAND_NEW_VIEWS,
32        // New views expand into view from height 0.  Existing views are updated and translated
33        // to their new positions if appropriate.  This animation is done for all views
34        // simultaneously without a cascade effect.
35        EXPAND_NEW_VIEWS_NO_CASCADE,
36        // New views are flown in from the bottom.  Existing views are updated and translated
37        // to their new positions if appropriate.
38        FLY_IN_NEW_VIEWS,
39        // New views are slid in from the side.  Existing views are updated and translated
40        // to their new positions if appropriate.
41        SLIDE_IN_NEW_VIEWS,
42        // Fade in all new views
43        FADE,
44    }
45
46    /**
47     * Supported exit animations for views in the {@link StaggeredGridView}.
48     */
49    public enum AnimationOut {
50        NONE,
51        // Stale views are faded out of view.  Existing views are then updated and translated
52        // to their new positions if appropriate.
53        FADE,
54        // Stale views are dropped to the bottom of the screen.  Existing views are then updated
55        // and translated to their new positions if appropriate.
56        FLY_DOWN,
57        // Stale views are slid to the side of the screen.  Existing views are then updated
58        // and translated to their new positions if appropriate.
59        SLIDE,
60        // Stale views are collapsed to height 0.  Existing views are then updated
61        // and translated to their new positions if appropriate.
62        COLLAPSE
63    }
64
65    private static Interpolator sDecelerateQuintInterpolator;
66
67    private static final int ANIMATION_LONG_SCREEN_SIZE = 1600;
68    private static final int ANIMATION_MED_SCREEN_SIZE = 1200;
69
70    // Duration of an individual animation when the children of the grid are laid out again. These
71    // are measured in milliseconds and based on the height of the screen.
72    private static final int ANIMATION_SHORT_DURATION = 400;
73    private static final int ANIMATION_MED_DURATION = 450;
74    private static final int ANIMATION_LONG_DURATION = 500;
75
76    public static final float ANIMATION_ROTATION_DEGREES = 25.0f;
77
78    /**
79     * Duration of an individual animation when the children of the grid are laid out again.
80     * This is measured in milliseconds.
81     */
82    private static int sAnimationDuration = ANIMATION_MED_DURATION;
83
84    public static void initialize(Context context) {
85        sDecelerateQuintInterpolator = AnimationUtils.loadInterpolator(context,
86                interpolator.decelerate_quint);
87
88        final Point size = new Point();
89        ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).
90                getDefaultDisplay().getSize(size);
91        final int screenHeight = size.y;
92
93        if (screenHeight >= ANIMATION_LONG_SCREEN_SIZE) {
94            sAnimationDuration = ANIMATION_LONG_DURATION;
95        } else if (screenHeight >= ANIMATION_MED_SCREEN_SIZE) {
96            sAnimationDuration = ANIMATION_MED_DURATION;
97        } else {
98            sAnimationDuration = ANIMATION_SHORT_DURATION;
99        }
100    }
101
102    public static int getDefaultAnimationDuration() {
103        return sAnimationDuration;
104    }
105
106    public static Interpolator getDefaultAnimationInterpolator() {
107        return sDecelerateQuintInterpolator;
108    }
109
110    /**
111     * Add animations to translate a view's X-translation.  {@link AnimatorListener} can be null.
112     */
113    private static void addXTranslationAnimators(List<Animator> animators,
114            final View view, int startTranslation, final int endTranslation, int animationDelay,
115            AnimatorListener listener) {
116        // We used to skip the animation if startTranslation == endTranslation,
117        // but to add a recycle view listener, we need at least one animation
118        view.setTranslationX(startTranslation);
119        final ObjectAnimator translateAnimatorX = ObjectAnimator.ofFloat(view,
120                View.TRANSLATION_X, startTranslation, endTranslation);
121        translateAnimatorX.setInterpolator(sDecelerateQuintInterpolator);
122        translateAnimatorX.setDuration(sAnimationDuration);
123        translateAnimatorX.setStartDelay(animationDelay);
124        if (listener != null) {
125            translateAnimatorX.addListener(listener);
126        }
127
128        animators.add(translateAnimatorX);
129    }
130
131    /**
132     * Add animations to translate a view's Y-translation.  {@link AnimatorListener} can be null.
133     */
134    private static void addYTranslationAnimators(List<Animator> animators,
135            final View view, int startTranslation, final int endTranslation, int animationDelay,
136            AnimatorListener listener) {
137        // We used to skip the animation if startTranslation == endTranslation,
138        // but to add a recycle view listener, we need at least one animation
139        view.setTranslationY(startTranslation);
140        final ObjectAnimator translateAnimatorY = ObjectAnimator.ofFloat(view,
141                View.TRANSLATION_Y, startTranslation, endTranslation);
142        translateAnimatorY.setInterpolator(sDecelerateQuintInterpolator);
143        translateAnimatorY.setDuration(sAnimationDuration);
144        translateAnimatorY.setStartDelay(animationDelay);
145
146        if (listener != null) {
147            translateAnimatorY.addListener(listener);
148        }
149
150        animators.add(translateAnimatorY);
151    }
152
153    /**
154     * Translate a view to the specified translation values, and animate the translations to 0.
155     */
156    public static void addXYTranslationAnimators(List<Animator> animators, final View view,
157            int xTranslation, int yTranslation, int animationDelay) {
158        addXTranslationAnimators(animators, view, xTranslation, 0, animationDelay, null);
159        addYTranslationAnimators(animators, view, yTranslation, 0, animationDelay, null);
160    }
161
162    /**
163     * Translate a view to the specified translation values, while rotating to the specified
164     * rotation value.
165     */
166    public static void addTranslationRotationAnimators(List<Animator> animators, final View view,
167            int xTranslation, int yTranslation, float rotation, int animationDelay) {
168        addXYTranslationAnimators(animators, view, xTranslation, yTranslation, animationDelay);
169
170        view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
171        view.setRotation(rotation);
172
173        final ObjectAnimator rotateAnimatorY = ObjectAnimator.ofFloat(view,
174                View.ROTATION, view.getRotation(), 0.0f);
175        rotateAnimatorY.setInterpolator(sDecelerateQuintInterpolator);
176        rotateAnimatorY.setDuration(sAnimationDuration);
177        rotateAnimatorY.setStartDelay(animationDelay);
178        rotateAnimatorY.addListener(new AnimatorListenerAdapter() {
179            private boolean mIsCanceled = false;
180
181            @Override
182            public void onAnimationCancel(Animator animation) {
183                mIsCanceled = true;
184            }
185
186            @Override
187            public void onAnimationEnd(Animator animation) {
188                if (!mIsCanceled) {
189                    view.setRotation(0);
190                }
191
192                view.setLayerType(View.LAYER_TYPE_NONE, null);
193            }
194        });
195
196        animators.add(rotateAnimatorY);
197    }
198
199    /**
200     * Expand a view into view by scaling up vertically from 0.
201     */
202    public static void addExpandInAnimators(List<Animator> animators, final View view,
203            int animationDelay) {
204        view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
205        view.setScaleY(0);
206
207        final ObjectAnimator scaleAnimatorY = ObjectAnimator.ofFloat(view,
208                View.SCALE_Y, view.getScaleY(), 1.0f);
209        scaleAnimatorY.setInterpolator(sDecelerateQuintInterpolator);
210        scaleAnimatorY.setDuration(sAnimationDuration);
211        scaleAnimatorY.setStartDelay(animationDelay);
212        scaleAnimatorY.addListener(new AnimatorListenerAdapter() {
213            @Override
214            public void onAnimationEnd(Animator animation) {
215                view.setScaleY(1.0f);
216                view.setLayerType(View.LAYER_TYPE_NONE, null);
217            }
218        });
219
220        animators.add(scaleAnimatorY);
221    }
222
223    /**
224     * Collapse a view out by scaling it from its current scaled value to 0.
225     */
226    public static void addCollapseOutAnimators(List<Animator> animators, final View view,
227            int animationDelay) {
228        view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
229
230        final ObjectAnimator scaleAnimatorY = ObjectAnimator.ofFloat(view, View.SCALE_Y,
231                view.getScaleY(), 0);
232        scaleAnimatorY.setInterpolator(sDecelerateQuintInterpolator);
233        scaleAnimatorY.setDuration(sAnimationDuration);
234        scaleAnimatorY.setStartDelay(animationDelay);
235        scaleAnimatorY.addListener(new AnimatorListenerAdapter() {
236            @Override
237            public void onAnimationEnd(Animator animation) {
238                view.setScaleY(0);
239                view.setLayerType(View.LAYER_TYPE_NONE, null);
240            }
241        });
242
243        animators.add(scaleAnimatorY);
244    }
245
246    /**
247     * Collapse a view out by scaling it from its current scaled value to 0.
248     * The animators are expected to run immediately without a start delay.
249     */
250    public static void addCollapseOutAnimators(List<Animator> animators, final View view) {
251        addCollapseOutAnimators(animators, view, 0 /* animation delay */);
252    }
253
254
255
256    /**
257     * Fly a view out by moving it vertically off the bottom of the screen.
258     */
259    public static void addFlyOutAnimators(List<Animator> animators,
260            final View view, int startTranslation, int endTranslation, int animationDelay) {
261        addYTranslationAnimators(animators, view, startTranslation, endTranslation,
262                animationDelay, null);
263    }
264
265    public static void addFlyOutAnimators(List<Animator> animators, final View view,
266            int startTranslation, int endTranslation) {
267        addFlyOutAnimators(animators, view, startTranslation,
268                endTranslation, 0 /* animation delay */);
269    }
270
271    public static void addSlideInFromRightAnimators(List<Animator> animators, final View view,
272            int startTranslation, int animationDelay) {
273        addXTranslationAnimators(animators, view, startTranslation, 0, animationDelay, null);
274        addFadeAnimators(animators, view, 0, 1.0f, animationDelay);
275    }
276
277    /**
278     * Slide a view out of view from the start to end position, fading the view out as it
279     * approaches the end position.
280     */
281    public static void addSlideOutAnimators(List<Animator> animators, final View view,
282            int startTranslation, int endTranslation, int animationDelay) {
283        addFadeAnimators(animators, view, view.getAlpha(), 0, animationDelay);
284        addXTranslationAnimators(animators, view, startTranslation, endTranslation,
285                animationDelay, null);
286    }
287
288    /**
289     * Slide a view out of view from the start to end position, fading the view out as it
290     * approaches the end position.  The animators are expected to run immediately without a
291     * start delay.
292     */
293    public static void addSlideOutAnimators(List<Animator> animators, final View view,
294            int startTranslation, int endTranslation) {
295        addSlideOutAnimators(animators, view, startTranslation,
296                endTranslation, 0 /* animation delay */);
297    }
298
299    /**
300     * Add animations to fade a view from the specified start alpha value to end value.
301     */
302    public static void addFadeAnimators(List<Animator> animators, final View view,
303            float startAlpha, final float endAlpha, int animationDelay) {
304        if (startAlpha == endAlpha) {
305            return;
306        }
307
308        view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
309        view.setAlpha(startAlpha);
310
311        final ObjectAnimator fadeAnimator = ObjectAnimator.ofFloat(view, View.ALPHA,
312                view.getAlpha(), endAlpha);
313        fadeAnimator.setInterpolator(sDecelerateQuintInterpolator);
314        fadeAnimator.setDuration(sAnimationDuration);
315        fadeAnimator.setStartDelay(animationDelay);
316        fadeAnimator.addListener(new AnimatorListenerAdapter() {
317            @Override
318            public void onAnimationEnd(Animator animation) {
319                view.setAlpha(endAlpha);
320                view.setLayerType(View.LAYER_TYPE_NONE, null);
321            }
322        });
323        animators.add(fadeAnimator);
324    }
325
326    /**
327     * Add animations to fade a view from the specified start alpha value to end value.
328     * The animators are expected to run immediately without a start delay.
329     */
330    public static void addFadeAnimators(List<Animator> animators, final View view,
331            float startAlpha, final float endAlpha) {
332        addFadeAnimators(animators, view, startAlpha, endAlpha, 0 /* animation delay */);
333    }
334}
335