1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tv.common.ui.setup.animation;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.AnimatorSet;
22 import android.animation.ObjectAnimator;
23 import android.animation.TypeEvaluator;
24 import android.content.Context;
25 import android.transition.Transition;
26 import android.transition.TransitionSet;
27 import android.view.Gravity;
28 import android.view.View;
29 import android.widget.ImageView;
30 
31 import com.android.tv.common.R;
32 
33 /**
34  * A helper class for setup animation.
35  */
36 public final class SetupAnimationHelper {
37     public static final long DELAY_BETWEEN_SIBLINGS_MS = applyAnimationTimeScale(33);
38 
39     private static final float ANIMATION_TIME_SCALE = 1.0f;
40 
41     private static boolean sInitialized;
42     private static long sFragmentTransitionDuration;
43     private static int sFragmentTransitionLongDistance;
44     private static int sFragmentTransitionShortDistance;
45 
SetupAnimationHelper()46     private SetupAnimationHelper() { }
47 
48     /**
49      * Load initial parameters. This method should be called before using this class.
50      */
initialize(Context context)51     public static void initialize(Context context) {
52         if (sInitialized) {
53             return;
54         }
55         sFragmentTransitionDuration = context.getResources()
56                 .getInteger(R.integer.setup_fragment_transition_duration);
57         sFragmentTransitionLongDistance = context.getResources()
58                 .getDimensionPixelOffset(R.dimen.setup_fragment_transition_long_distance);
59         sFragmentTransitionShortDistance = context.getResources()
60                 .getDimensionPixelOffset(R.dimen.setup_fragment_transition_short_distance);
61         sInitialized = true;
62     }
63 
checkInitialized()64     private static void checkInitialized() {
65         if (!sInitialized) {
66             throw new IllegalStateException("SetupAnimationHelper not initialized");
67         }
68     }
69 
70     public static class TransitionBuilder {
71         private int mSlideEdge = Gravity.START;
72         private final int mDistance = sFragmentTransitionLongDistance;
73         private long mDuration = sFragmentTransitionDuration;
74         private int[] mParentIdForDelay;
75         private int[] mExcludeIds;
76 
TransitionBuilder()77         public TransitionBuilder() {
78             checkInitialized();
79         }
80 
81         /**
82          * Sets the edge of the slide transition.
83          *
84          * @see android.transition.Slide#setSlideEdge
85          */
setSlideEdge(int slideEdge)86         public TransitionBuilder setSlideEdge(int slideEdge) {
87             mSlideEdge = slideEdge;
88             return this;
89         }
90 
91         /**
92          * Sets the duration of the transition.
93          */
setDuration(long duration)94         public TransitionBuilder setDuration(long duration) {
95             mDuration = duration;
96             return this;
97         }
98 
99         /**
100          * Sets the ID of the view whose descendants will perform delayed move.
101          *
102          * @see android.view.ViewGroup#isTransitionGroup
103          */
setParentIdsForDelay(int[] parentIdForDelay)104         public TransitionBuilder setParentIdsForDelay(int[] parentIdForDelay) {
105             mParentIdForDelay = parentIdForDelay;
106             return this;
107         }
108 
109         /**
110          * Sets the ID's of the views which will not be included in the transition.
111          */
setExcludeIds(int[] excludeIds)112         public TransitionBuilder setExcludeIds(int[] excludeIds) {
113             mExcludeIds = excludeIds;
114             return this;
115         }
116 
117         /**
118          * Builds and returns the {@link android.transition.Transition}.
119          */
build()120         public Transition build() {
121             FadeAndShortSlide transition = new FadeAndShortSlide(mSlideEdge, mParentIdForDelay);
122             transition.setDistance(mDistance);
123             transition.setDuration(mDuration);
124             if (mExcludeIds != null) {
125                 for (int id : mExcludeIds) {
126                     transition.excludeTarget(id, true);
127                 }
128             }
129             return transition;
130         }
131     }
132 
133     /**
134      * Changes the move distance of the {@code transition} to long distance.
135      */
setLongDistance(FadeAndShortSlide transition)136     public static void setLongDistance(FadeAndShortSlide transition) {
137         checkInitialized();
138         transition.setDistance(sFragmentTransitionLongDistance);
139     }
140 
141     /**
142      * Changes the move distance of the {@code transition} to short distance.
143      */
setShortDistance(FadeAndShortSlide transition)144     public static void setShortDistance(FadeAndShortSlide transition) {
145         checkInitialized();
146         transition.setDistance(sFragmentTransitionShortDistance);
147     }
148 
149     /**
150      * Applies the animation scale to the given {@code animator}.
151      */
applyAnimationTimeScale(Animator animator)152     public static Animator applyAnimationTimeScale(Animator animator) {
153         if (animator instanceof AnimatorSet) {
154             for (Animator child : ((AnimatorSet) animator).getChildAnimations()) {
155                 applyAnimationTimeScale(child);
156             }
157         }
158         if (animator.getDuration() > 0) {
159             animator.setDuration((long) (animator.getDuration() * ANIMATION_TIME_SCALE));
160         }
161         animator.setStartDelay((long) (animator.getStartDelay() * ANIMATION_TIME_SCALE));
162         return animator;
163     }
164 
165     /**
166      * Applies the animation scale to the given {@code transition}.
167      */
applyAnimationTimeScale(Transition transition)168     public static Transition applyAnimationTimeScale(Transition transition) {
169         if (transition instanceof TransitionSet) {
170             TransitionSet set = (TransitionSet) transition;
171             int count = set.getTransitionCount();
172             for (int i = 0; i < count; ++i) {
173                 applyAnimationTimeScale(set.getTransitionAt(i));
174             }
175         }
176         if (transition.getDuration() > 0) {
177             transition.setDuration((long) (transition.getDuration() * ANIMATION_TIME_SCALE));
178         }
179         transition.setStartDelay((long) (transition.getStartDelay() * ANIMATION_TIME_SCALE));
180         return transition;
181     }
182 
183     /**
184      * Applies the animation scale to the given {@code time}.
185      */
applyAnimationTimeScale(long time)186     public static long applyAnimationTimeScale(long time) {
187         return (long) (time * ANIMATION_TIME_SCALE);
188     }
189 
190     /**
191      * Returns an animator which animates the source image of the {@link ImageView}.
192      *
193      * <p>The frame rate is 60 fps.
194      */
createFrameAnimator(ImageView imageView, int[] frames)195     public static ObjectAnimator createFrameAnimator(ImageView imageView, int[] frames) {
196         return createFrameAnimatorWithDelay(imageView, frames, 0);
197     }
198 
199     /**
200      * Returns an animator which animates the source image of the {@link ImageView} with start delay.
201      *
202      * <p>The frame rate is 60 fps.
203      */
createFrameAnimatorWithDelay(ImageView imageView, int[] frames, long startDelay)204     public static ObjectAnimator createFrameAnimatorWithDelay(ImageView imageView, int[] frames,
205             long startDelay) {
206         ObjectAnimator animator = ObjectAnimator.ofInt(imageView, "imageResource", frames);
207         // Make it 60 fps.
208         animator.setDuration(frames.length * 1000 / 60);
209         animator.setInterpolator(null);
210         animator.setStartDelay(startDelay);
211         animator.setEvaluator(new TypeEvaluator<Integer>() {
212             @Override
213             public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
214                 return startValue;
215             }
216         });
217         return animator;
218     }
219 
220     /**
221      * Creates a fade out animator.
222      *
223      * @param view The view which will be animated.
224      * @param duration The duration of the animation.
225      * @param makeVisibleAfterAnimation If {@code true}, the view will become visible after the
226      * animation ends.
227      */
createFadeOutAnimator(final View view, long duration, boolean makeVisibleAfterAnimation)228     public static Animator createFadeOutAnimator(final View view, long duration,
229             boolean makeVisibleAfterAnimation) {
230         ObjectAnimator animator =
231                 ObjectAnimator.ofFloat(view, View.ALPHA, 1.0f, 0.0f).setDuration(duration);
232         if (makeVisibleAfterAnimation) {
233             animator.addListener(new AnimatorListenerAdapter() {
234                 @Override
235                 public void onAnimationEnd(Animator animation) {
236                     view.setAlpha(1.0f);
237                 }
238             });
239         }
240         return animator;
241     }
242 }
243