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         if (view != null) {
184             view.setAlpha(appearing ? 0f : 1.0f);
185             view.setTranslationY(appearing ? translationY : 0);
186             Animator alphaAnim;
187             float targetAlpha =  appearing ? 1f : 0f;
188             if (view.isHardwareAccelerated()) {
189                 RenderNodeAnimator alphaAnimRt = new RenderNodeAnimator(RenderNodeAnimator.ALPHA,
190                         targetAlpha);
191                 alphaAnimRt.setTarget(view);
192                 alphaAnim = alphaAnimRt;
193             } else {
194                 alphaAnim = ObjectAnimator.ofFloat(view, View.ALPHA, view.getAlpha(), targetAlpha);
195             }
196             alphaAnim.setInterpolator(interpolator);
197             alphaAnim.setDuration(duration);
198             alphaAnim.setStartDelay(delay);
199             if (view.hasOverlappingRendering()) {
200                 view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
201                 alphaAnim.addListener(new AnimatorListenerAdapter() {
202                     @Override
203                     public void onAnimationEnd(Animator animation) {
204                         view.setLayerType(View.LAYER_TYPE_NONE, null);
205                     }
206                 });
207             }
208             if (endRunnable != null) {
209                 alphaAnim.addListener(new AnimatorListenerAdapter() {
210                     @Override
211                     public void onAnimationEnd(Animator animation) {
212                         endRunnable.run();
213                     }
214                 });
215             }
216             alphaAnim.start();
217             startTranslationYAnimation(view, delay, duration, appearing ? 0 : translationY,
218                     interpolator);
219         }
220     }
221 
startTranslationYAnimation(View view, long delay, long duration, float endTranslationY, Interpolator interpolator)222     public static void startTranslationYAnimation(View view, long delay, long duration,
223             float endTranslationY, Interpolator interpolator) {
224         Animator translationAnim;
225         if (view.isHardwareAccelerated()) {
226             RenderNodeAnimator translationAnimRt = new RenderNodeAnimator(
227                     RenderNodeAnimator.TRANSLATION_Y, endTranslationY);
228             translationAnimRt.setTarget(view);
229             translationAnim = translationAnimRt;
230         } else {
231             translationAnim = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y,
232                     view.getTranslationY(), endTranslationY);
233         }
234         translationAnim.setInterpolator(interpolator);
235         translationAnim.setDuration(duration);
236         translationAnim.setStartDelay(delay);
237         translationAnim.start();
238     }
239 
240     public class AppearAnimationProperties {
241         public long[][] delays;
242         public int maxDelayRowIndex;
243         public int maxDelayColIndex;
244     }
245 
246     public interface RowTranslationScaler {
getRowTranslationScale(int row, int numRows)247         float getRowTranslationScale(int row, int numRows);
248     }
249 }
250