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 import com.android.tv.common.R;
31 
32 /** A helper class for setup animation. */
33 public final class SetupAnimationHelper {
34     public static final long DELAY_BETWEEN_SIBLINGS_MS = applyAnimationTimeScale(33);
35 
36     private static final float ANIMATION_TIME_SCALE = 1.0f;
37 
38     private static boolean sInitialized;
39     private static long sFragmentTransitionDuration;
40     private static int sFragmentTransitionLongDistance;
41     private static int sFragmentTransitionShortDistance;
42 
SetupAnimationHelper()43     private SetupAnimationHelper() {}
44 
45     /** Load initial parameters. This method should be called before using this class. */
initialize(Context context)46     public static void initialize(Context context) {
47         if (sInitialized) {
48             return;
49         }
50         sFragmentTransitionDuration =
51                 context.getResources().getInteger(R.integer.setup_fragment_transition_duration);
52         sFragmentTransitionLongDistance =
53                 context.getResources()
54                         .getDimensionPixelOffset(R.dimen.setup_fragment_transition_long_distance);
55         sFragmentTransitionShortDistance =
56                 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 final 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         /** Sets the duration of the transition. */
setDuration(long duration)89         public TransitionBuilder setDuration(long duration) {
90             mDuration = duration;
91             return this;
92         }
93 
94         /**
95          * Sets the ID of the view whose descendants will perform delayed move.
96          *
97          * @see android.view.ViewGroup#isTransitionGroup
98          */
setParentIdsForDelay(int[] parentIdForDelay)99         public TransitionBuilder setParentIdsForDelay(int[] parentIdForDelay) {
100             mParentIdForDelay = parentIdForDelay;
101             return this;
102         }
103 
104         /** Sets the ID's of the views which will not be included in the transition. */
setExcludeIds(int[] excludeIds)105         public TransitionBuilder setExcludeIds(int[] excludeIds) {
106             mExcludeIds = excludeIds;
107             return this;
108         }
109 
110         /** Builds and returns the {@link android.transition.Transition}. */
build()111         public Transition build() {
112             FadeAndShortSlide transition = new FadeAndShortSlide(mSlideEdge, mParentIdForDelay);
113             transition.setDistance(mDistance);
114             transition.setDuration(mDuration);
115             if (mExcludeIds != null) {
116                 for (int id : mExcludeIds) {
117                     transition.excludeTarget(id, true);
118                 }
119             }
120             return transition;
121         }
122     }
123 
124     /** Changes the move distance of the {@code transition} to long distance. */
setLongDistance(FadeAndShortSlide transition)125     public static void setLongDistance(FadeAndShortSlide transition) {
126         checkInitialized();
127         transition.setDistance(sFragmentTransitionLongDistance);
128     }
129 
130     /** Changes the move distance of the {@code transition} to short distance. */
setShortDistance(FadeAndShortSlide transition)131     public static void setShortDistance(FadeAndShortSlide transition) {
132         checkInitialized();
133         transition.setDistance(sFragmentTransitionShortDistance);
134     }
135 
136     /** Applies the animation scale to the given {@code animator}. */
applyAnimationTimeScale(Animator animator)137     public static Animator applyAnimationTimeScale(Animator animator) {
138         if (animator instanceof AnimatorSet) {
139             for (Animator child : ((AnimatorSet) animator).getChildAnimations()) {
140                 applyAnimationTimeScale(child);
141             }
142         }
143         if (animator.getDuration() > 0) {
144             animator.setDuration((long) (animator.getDuration() * ANIMATION_TIME_SCALE));
145         }
146         animator.setStartDelay((long) (animator.getStartDelay() * ANIMATION_TIME_SCALE));
147         return animator;
148     }
149 
150     /** Applies the animation scale to the given {@code transition}. */
applyAnimationTimeScale(Transition transition)151     public static Transition applyAnimationTimeScale(Transition transition) {
152         if (transition instanceof TransitionSet) {
153             TransitionSet set = (TransitionSet) transition;
154             int count = set.getTransitionCount();
155             for (int i = 0; i < count; ++i) {
156                 applyAnimationTimeScale(set.getTransitionAt(i));
157             }
158         }
159         if (transition.getDuration() > 0) {
160             transition.setDuration((long) (transition.getDuration() * ANIMATION_TIME_SCALE));
161         }
162         transition.setStartDelay((long) (transition.getStartDelay() * ANIMATION_TIME_SCALE));
163         return transition;
164     }
165 
166     /** Applies the animation scale to the given {@code time}. */
applyAnimationTimeScale(long time)167     public static long applyAnimationTimeScale(long time) {
168         return (long) (time * ANIMATION_TIME_SCALE);
169     }
170 
171     /**
172      * Returns an animator which animates the source image of the {@link ImageView}.
173      *
174      * <p>The frame rate is 60 fps.
175      */
createFrameAnimator(ImageView imageView, int[] frames)176     public static ObjectAnimator createFrameAnimator(ImageView imageView, int[] frames) {
177         return createFrameAnimatorWithDelay(imageView, frames, 0);
178     }
179 
180     /**
181      * Returns an animator which animates the source image of the {@link ImageView} with start
182      * delay.
183      *
184      * <p>The frame rate is 60 fps.
185      */
createFrameAnimatorWithDelay( ImageView imageView, int[] frames, long startDelay)186     public static ObjectAnimator createFrameAnimatorWithDelay(
187             ImageView imageView, int[] frames, long startDelay) {
188         ObjectAnimator animator = ObjectAnimator.ofInt(imageView, "imageResource", frames);
189         // Make it 60 fps.
190         animator.setDuration(frames.length * 1000 / 60);
191         animator.setInterpolator(null);
192         animator.setStartDelay(startDelay);
193         animator.setEvaluator(
194                 new TypeEvaluator<Integer>() {
195                     @Override
196                     public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
197                         return startValue;
198                     }
199                 });
200         return animator;
201     }
202 
203     /**
204      * Creates a fade out animator.
205      *
206      * @param view The view which will be animated.
207      * @param duration The duration of the animation.
208      * @param makeVisibleAfterAnimation If {@code true}, the view will become visible after the
209      *     animation ends.
210      */
createFadeOutAnimator( final View view, long duration, boolean makeVisibleAfterAnimation)211     public static Animator createFadeOutAnimator(
212             final View view, long duration, boolean makeVisibleAfterAnimation) {
213         ObjectAnimator animator =
214                 ObjectAnimator.ofFloat(view, View.ALPHA, 1.0f, 0.0f).setDuration(duration);
215         if (makeVisibleAfterAnimation) {
216             animator.addListener(
217                     new AnimatorListenerAdapter() {
218                         @Override
219                         public void onAnimationEnd(Animator animation) {
220                             view.setAlpha(1.0f);
221                         }
222                     });
223         }
224         return animator;
225     }
226 }
227