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         sFragmentTransitionDuration = context.getResources()
53                 .getInteger(R.integer.setup_fragment_transition_duration);
54         sFragmentTransitionLongDistance = context.getResources()
55                 .getDimensionPixelOffset(R.dimen.setup_fragment_transition_long_distance);
56         sFragmentTransitionShortDistance = context.getResources()
57                 .getDimensionPixelOffset(R.dimen.setup_fragment_transition_short_distance);
58         sInitialized = true;
59     }
60 
checkInitialized()61     private static void checkInitialized() {
62         if (!sInitialized) {
63             throw new IllegalStateException("SetupAnimationHelper not initialized");
64         }
65     }
66 
67     public static class TransitionBuilder {
68         private int mSlideEdge = Gravity.START;
69         private int mDistance = sFragmentTransitionLongDistance;
70         private long mDuration = sFragmentTransitionDuration;
71         private int[] mParentIdForDelay;
72         private int[] mExcludeIds;
73 
TransitionBuilder()74         public TransitionBuilder() {
75             checkInitialized();
76         }
77 
78         /**
79          * Sets the edge of the slide transition.
80          *
81          * @see android.transition.Slide#setSlideEdge
82          */
setSlideEdge(int slideEdge)83         public TransitionBuilder setSlideEdge(int slideEdge) {
84             mSlideEdge = slideEdge;
85             return this;
86         }
87 
88         /**
89          * Sets the duration of the transition.
90          */
setDuration(long duration)91         public TransitionBuilder setDuration(long duration) {
92             mDuration = duration;
93             return this;
94         }
95 
96         /**
97          * Sets the ID of the view whose descendants will perform delayed move.
98          *
99          * @see android.view.ViewGroup#isTransitionGroup
100          */
setParentIdsForDelay(int[] parentIdForDelay)101         public TransitionBuilder setParentIdsForDelay(int[] parentIdForDelay) {
102             mParentIdForDelay = parentIdForDelay;
103             return this;
104         }
105 
106         /**
107          * Sets the ID's of the views which will not be included in the transition.
108          */
setExcludeIds(int[] excludeIds)109         public TransitionBuilder setExcludeIds(int[] excludeIds) {
110             mExcludeIds = excludeIds;
111             return this;
112         }
113 
114         /**
115          * Builds and returns the {@link android.transition.Transition}.
116          */
build()117         public Transition build() {
118             FadeAndShortSlide transition = new FadeAndShortSlide(mSlideEdge, mParentIdForDelay);
119             transition.setDistance(mDistance);
120             transition.setDuration(mDuration);
121             if (mExcludeIds != null) {
122                 for (int id : mExcludeIds) {
123                     transition.excludeTarget(id, true);
124                 }
125             }
126             return transition;
127         }
128     }
129 
130     /**
131      * Changes the move distance of the {@code transition} to long distance.
132      */
setLongDistance(FadeAndShortSlide transition)133     public static void setLongDistance(FadeAndShortSlide transition) {
134         checkInitialized();
135         transition.setDistance(sFragmentTransitionLongDistance);
136     }
137 
138     /**
139      * Changes the move distance of the {@code transition} to short distance.
140      */
setShortDistance(FadeAndShortSlide transition)141     public static void setShortDistance(FadeAndShortSlide transition) {
142         checkInitialized();
143         transition.setDistance(sFragmentTransitionShortDistance);
144     }
145 
146     /**
147      * Applies the animation scale to the given {@code animator}.
148      */
applyAnimationTimeScale(Animator animator)149     public static Animator applyAnimationTimeScale(Animator animator) {
150         if (animator instanceof AnimatorSet) {
151             for (Animator child : ((AnimatorSet) animator).getChildAnimations()) {
152                 applyAnimationTimeScale(child);
153             }
154         }
155         if (animator.getDuration() > 0) {
156             animator.setDuration((long) (animator.getDuration() * ANIMATION_TIME_SCALE));
157         }
158         animator.setStartDelay((long) (animator.getStartDelay() * ANIMATION_TIME_SCALE));
159         return animator;
160     }
161 
162     /**
163      * Applies the animation scale to the given {@code transition}.
164      */
applyAnimationTimeScale(Transition transition)165     public static Transition applyAnimationTimeScale(Transition transition) {
166         if (transition instanceof TransitionSet) {
167             TransitionSet set = (TransitionSet) transition;
168             int count = set.getTransitionCount();
169             for (int i = 0; i < count; ++i) {
170                 applyAnimationTimeScale(set.getTransitionAt(i));
171             }
172         }
173         if (transition.getDuration() > 0) {
174             transition.setDuration((long) (transition.getDuration() * ANIMATION_TIME_SCALE));
175         }
176         transition.setStartDelay((long) (transition.getStartDelay() * ANIMATION_TIME_SCALE));
177         return transition;
178     }
179 
180     /**
181      * Applies the animation scale to the given {@code time}.
182      */
applyAnimationTimeScale(long time)183     public static long applyAnimationTimeScale(long time) {
184         return (long) (time * ANIMATION_TIME_SCALE);
185     }
186 
187     /**
188      * Returns an animator which animates the source image of the {@link ImageView}.
189      *
190      * <p>The frame rate is 60 fps.
191      */
createFrameAnimator(ImageView imageView, int[] frames)192     public static ObjectAnimator createFrameAnimator(ImageView imageView, int[] frames) {
193         return createFrameAnimatorWithDelay(imageView, frames, 0);
194     }
195 
196     /**
197      * Returns an animator which animates the source image of the {@link ImageView} with start delay.
198      *
199      * <p>The frame rate is 60 fps.
200      */
createFrameAnimatorWithDelay(ImageView imageView, int[] frames, long startDelay)201     public static ObjectAnimator createFrameAnimatorWithDelay(ImageView imageView, int[] frames,
202             long startDelay) {
203         ObjectAnimator animator = ObjectAnimator.ofInt(imageView, "imageResource", frames);
204         // Make it 60 fps.
205         animator.setDuration(frames.length * 1000 / 60);
206         animator.setInterpolator(null);
207         animator.setStartDelay(startDelay);
208         animator.setEvaluator(new TypeEvaluator<Integer>() {
209             @Override
210             public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
211                 return startValue;
212             }
213         });
214         return animator;
215     }
216 
217     /**
218      * Creates a fade out animator.
219      *
220      * @param view The view which will be animated.
221      * @param duration The duration of the animation.
222      * @param makeVisibleAfterAnimation If {@code true}, the view will become visible after the
223      * animation ends.
224      */
createFadeOutAnimator(final View view, long duration, boolean makeVisibleAfterAnimation)225     public static Animator createFadeOutAnimator(final View view, long duration,
226             boolean makeVisibleAfterAnimation) {
227         ObjectAnimator animator =
228                 ObjectAnimator.ofFloat(view, View.ALPHA, 1.0f, 0.0f).setDuration(duration);
229         if (makeVisibleAfterAnimation) {
230             animator.addListener(new AnimatorListenerAdapter() {
231                 @Override
232                 public void onAnimationEnd(Animator animation) {
233                     view.setAlpha(1.0f);
234                 }
235             });
236         }
237         return animator;
238     }
239 }
240