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.settingslib.animation;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.ObjectAnimator;
22 import android.content.Context;
23 import android.view.RenderNodeAnimator;
24 import android.view.View;
25 import android.view.animation.AnimationUtils;
26 import android.view.animation.Interpolator;
27 
28 import com.android.settingslib.R;
29 
30 /**
31  * A class to make nice appear transitions for views in a tabular layout.
32  */
33 public class AppearAnimationUtils implements AppearAnimationCreator<View> {
34 
35     public static final long DEFAULT_APPEAR_DURATION = 220;
36 
37     private final Interpolator mInterpolator;
38     private final float mStartTranslation;
39     private final AppearAnimationProperties mProperties = new AppearAnimationProperties();
40     protected final float mDelayScale;
41     private final long mDuration;
42     protected RowTranslationScaler mRowTranslationScaler;
43     protected boolean mAppearing;
44 
AppearAnimationUtils(Context ctx)45     public AppearAnimationUtils(Context ctx) {
46         this(ctx, DEFAULT_APPEAR_DURATION,
47                 1.0f, 1.0f,
48                 AnimationUtils.loadInterpolator(ctx, android.R.interpolator.linear_out_slow_in));
49     }
50 
AppearAnimationUtils(Context ctx, long duration, float translationScaleFactor, float delayScaleFactor, Interpolator interpolator)51     public AppearAnimationUtils(Context ctx, long duration, float translationScaleFactor,
52             float delayScaleFactor, Interpolator interpolator) {
53         mInterpolator = interpolator;
54         mStartTranslation = ctx.getResources().getDimensionPixelOffset(
55                 R.dimen.appear_y_translation_start) * translationScaleFactor;
56         mDelayScale = delayScaleFactor;
57         mDuration = duration;
58         mAppearing = true;
59     }
60 
startAnimation2d(View[][] objects, final Runnable finishListener)61     public void startAnimation2d(View[][] objects, final Runnable finishListener) {
62         startAnimation2d(objects, finishListener, this);
63     }
64 
startAnimation(View[] objects, final Runnable finishListener)65     public void startAnimation(View[] objects, final Runnable finishListener) {
66         startAnimation(objects, finishListener, this);
67     }
68 
startAnimation2d(T[][] objects, final Runnable finishListener, AppearAnimationCreator<T> creator)69     public <T> void startAnimation2d(T[][] objects, final Runnable finishListener,
70             AppearAnimationCreator<T> creator) {
71         AppearAnimationProperties properties = getDelays(objects);
72         startAnimations(properties, objects, finishListener, creator);
73     }
74 
startAnimation(T[] objects, final Runnable finishListener, AppearAnimationCreator<T> creator)75     public <T> void startAnimation(T[] objects, final Runnable finishListener,
76             AppearAnimationCreator<T> creator) {
77         AppearAnimationProperties properties = getDelays(objects);
78         startAnimations(properties, objects, finishListener, creator);
79     }
80 
startAnimations(AppearAnimationProperties properties, T[] objects, final Runnable finishListener, AppearAnimationCreator<T> creator)81     private <T> void startAnimations(AppearAnimationProperties properties, T[] objects,
82             final Runnable finishListener, AppearAnimationCreator<T> creator) {
83         if (properties.maxDelayRowIndex == -1 || properties.maxDelayColIndex == -1) {
84             finishListener.run();
85             return;
86         }
87         for (int row = 0; row < properties.delays.length; row++) {
88             long[] columns = properties.delays[row];
89             long delay = columns[0];
90             Runnable endRunnable = null;
91             if (properties.maxDelayRowIndex == row && properties.maxDelayColIndex == 0) {
92                 endRunnable = finishListener;
93             }
94             float translationScale = mRowTranslationScaler != null
95                     ? mRowTranslationScaler.getRowTranslationScale(row, properties.delays.length)
96                     : 1f;
97             float translation = translationScale * mStartTranslation;
98             creator.createAnimation(objects[row], delay, mDuration,
99                     mAppearing ? translation : -translation,
100                     mAppearing, mInterpolator, endRunnable);
101         }
102     }
103 
startAnimations(AppearAnimationProperties properties, T[][] objects, final Runnable finishListener, AppearAnimationCreator<T> creator)104     private <T> void startAnimations(AppearAnimationProperties properties, T[][] objects,
105             final Runnable finishListener, AppearAnimationCreator<T> creator) {
106         if (properties.maxDelayRowIndex == -1 || properties.maxDelayColIndex == -1) {
107             finishListener.run();
108             return;
109         }
110         for (int row = 0; row < properties.delays.length; row++) {
111             long[] columns = properties.delays[row];
112             float translationScale = mRowTranslationScaler != null
113                     ? mRowTranslationScaler.getRowTranslationScale(row, properties.delays.length)
114                     : 1f;
115             float translation = translationScale * mStartTranslation;
116             for (int col = 0; col < columns.length; col++) {
117                 long delay = columns[col];
118                 Runnable endRunnable = null;
119                 if (properties.maxDelayRowIndex == row && properties.maxDelayColIndex == col) {
120                     endRunnable = finishListener;
121                 }
122                 creator.createAnimation(objects[row][col], delay, mDuration,
123                         mAppearing ? translation : -translation,
124                         mAppearing, mInterpolator, endRunnable);
125             }
126         }
127     }
128 
getDelays(T[] items)129     private <T> AppearAnimationProperties getDelays(T[] items) {
130         long maxDelay = -1;
131         mProperties.maxDelayColIndex = -1;
132         mProperties.maxDelayRowIndex = -1;
133         mProperties.delays = new long[items.length][];
134         for (int row = 0; row < items.length; row++) {
135             mProperties.delays[row] = new long[1];
136             long delay = calculateDelay(row, 0);
137             mProperties.delays[row][0] = delay;
138             if (items[row] != null && delay > maxDelay) {
139                 maxDelay = delay;
140                 mProperties.maxDelayColIndex = 0;
141                 mProperties.maxDelayRowIndex = row;
142             }
143         }
144         return mProperties;
145     }
146 
getDelays(T[][] items)147     private <T> AppearAnimationProperties getDelays(T[][] items) {
148         long maxDelay = -1;
149         mProperties.maxDelayColIndex = -1;
150         mProperties.maxDelayRowIndex = -1;
151         mProperties.delays = new long[items.length][];
152         for (int row = 0; row < items.length; row++) {
153             T[] columns = items[row];
154             mProperties.delays[row] = new long[columns.length];
155             for (int col = 0; col < columns.length; col++) {
156                 long delay = calculateDelay(row, col);
157                 mProperties.delays[row][col] = delay;
158                 if (items[row][col] != null && delay > maxDelay) {
159                     maxDelay = delay;
160                     mProperties.maxDelayColIndex = col;
161                     mProperties.maxDelayRowIndex = row;
162                 }
163             }
164         }
165         return mProperties;
166     }
167 
calculateDelay(int row, int col)168     protected long calculateDelay(int row, int col) {
169         return (long) ((row * 40 + col * (Math.pow(row, 0.4) + 0.4) * 20) * mDelayScale);
170     }
171 
getInterpolator()172     public Interpolator getInterpolator() {
173         return mInterpolator;
174     }
175 
getStartTranslation()176     public float getStartTranslation() {
177         return mStartTranslation;
178     }
179 
180     @Override
createAnimation(final View view, long delay, long duration, float translationY, boolean appearing, Interpolator interpolator, final Runnable endRunnable)181     public void createAnimation(final View view, long delay, long duration, float translationY,
182             boolean appearing, Interpolator interpolator, final Runnable endRunnable) {
183         createAnimation(
184                 view, delay, duration, translationY, appearing, interpolator, endRunnable, null);
185     }
186 
187     @Override
createAnimation(final View view, long delay, long duration, float translationY, boolean appearing, Interpolator interpolator, final Runnable endRunnable, final AnimatorListenerAdapter animatorListener)188     public void createAnimation(final View view, long delay,
189             long duration, float translationY, boolean appearing, Interpolator interpolator,
190             final Runnable endRunnable, final AnimatorListenerAdapter animatorListener) {
191         if (view != null) {
192             float targetAlpha = appearing ? 1f : 0f;
193             float targetTranslationY = appearing ? 0 : translationY;
194             view.setAlpha(1.0f - targetAlpha);
195             view.setTranslationY(translationY - targetTranslationY);
196             Animator alphaAnim;
197 
198             if (view.isHardwareAccelerated()) {
199                 RenderNodeAnimator alphaAnimRt = new RenderNodeAnimator(RenderNodeAnimator.ALPHA,
200                         targetAlpha);
201                 alphaAnimRt.setTarget(view);
202                 alphaAnim = alphaAnimRt;
203             } else {
204                 alphaAnim = ObjectAnimator.ofFloat(view, View.ALPHA, view.getAlpha(), targetAlpha);
205             }
206             alphaAnim.setInterpolator(interpolator);
207             alphaAnim.setDuration(duration);
208             alphaAnim.setStartDelay(delay);
209             if (view.hasOverlappingRendering()) {
210                 view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
211                 alphaAnim.addListener(new AnimatorListenerAdapter() {
212                     @Override
213                     public void onAnimationEnd(Animator animation) {
214                         view.setLayerType(View.LAYER_TYPE_NONE, null);
215                     }
216                 });
217             }
218             alphaAnim.addListener(new AnimatorListenerAdapter() {
219                 @Override
220                 public void onAnimationEnd(Animator animation) {
221                     view.setAlpha(targetAlpha);
222                     if (endRunnable != null) {
223                         endRunnable.run();
224                     }
225                 }
226             });
227             alphaAnim.start();
228             startTranslationYAnimation(view, delay, duration, targetTranslationY,
229                     interpolator, animatorListener);
230         }
231     }
232 
233     /**
234      * A static method to start translation y animation
235      */
startTranslationYAnimation(View view, long delay, long duration, float endTranslationY, Interpolator interpolator)236     public static void startTranslationYAnimation(View view, long delay, long duration,
237             float endTranslationY, Interpolator interpolator) {
238         startTranslationYAnimation(view, delay, duration, endTranslationY, interpolator, null);
239     }
240 
241     /**
242      * A static method to start translation y animation
243      */
startTranslationYAnimation(View view, long delay, long duration, float endTranslationY, Interpolator interpolator, AnimatorListenerAdapter listener)244     public static void startTranslationYAnimation(View view, long delay, long duration,
245             float endTranslationY, Interpolator interpolator, AnimatorListenerAdapter listener) {
246         Animator translationAnim;
247         if (view.isHardwareAccelerated()) {
248             RenderNodeAnimator translationAnimRt = new RenderNodeAnimator(
249                     RenderNodeAnimator.TRANSLATION_Y, endTranslationY);
250             translationAnimRt.setTarget(view);
251             translationAnim = translationAnimRt;
252         } else {
253             translationAnim = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y,
254                     view.getTranslationY(), endTranslationY);
255         }
256         translationAnim.setInterpolator(interpolator);
257         translationAnim.setDuration(duration);
258         translationAnim.setStartDelay(delay);
259         if (listener != null) {
260             translationAnim.addListener(listener);
261         }
262         translationAnim.addListener(new AnimatorListenerAdapter() {
263             @Override
264             public void onAnimationEnd(Animator animation) {
265                 view.setTranslationY(endTranslationY);
266             }
267         });
268         translationAnim.start();
269     }
270 
271     public class AppearAnimationProperties {
272         public long[][] delays;
273         public int maxDelayRowIndex;
274         public int maxDelayColIndex;
275     }
276 
277     public interface RowTranslationScaler {
getRowTranslationScale(int row, int numRows)278         float getRowTranslationScale(int row, int numRows);
279     }
280 }
281