• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.systemui.volume;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.TimeInterpolator;
22 import android.animation.ValueAnimator;
23 import android.animation.ValueAnimator.AnimatorUpdateListener;
24 import android.app.Dialog;
25 import android.content.DialogInterface;
26 import android.content.DialogInterface.OnDismissListener;
27 import android.content.DialogInterface.OnShowListener;
28 import android.graphics.drawable.Drawable;
29 import android.os.Handler;
30 import android.util.Log;
31 import android.view.View;
32 import android.view.ViewGroup;
33 import android.view.animation.PathInterpolator;
34 
35 public class VolumeDialogMotion {
36     private static final String TAG = Util.logTag(VolumeDialogMotion.class);
37 
38     private static final float ANIMATION_SCALE = 1.0f;
39     private static final int PRE_DISMISS_DELAY = 50;
40 
41     private final Dialog mDialog;
42     private final View mDialogView;
43     private final ViewGroup mContents;  // volume rows + zen footer
44     private final View mChevron;
45     private final Handler mHandler = new Handler();
46     private final Callback mCallback;
47 
48     private boolean mAnimating;  // show or dismiss animation is running
49     private boolean mShowing;  // show animation is running
50     private boolean mDismissing;  // dismiss animation is running
51     private ValueAnimator mChevronPositionAnimator;
52     private ValueAnimator mContentsPositionAnimator;
53 
VolumeDialogMotion(Dialog dialog, View dialogView, ViewGroup contents, View chevron, Callback callback)54     public VolumeDialogMotion(Dialog dialog, View dialogView, ViewGroup contents, View chevron,
55             Callback callback) {
56         mDialog = dialog;
57         mDialogView = dialogView;
58         mContents = contents;
59         mChevron = chevron;
60         mCallback = callback;
61         mDialog.setOnDismissListener(new OnDismissListener() {
62             @Override
63             public void onDismiss(DialogInterface dialog) {
64                 if (D.BUG) Log.d(TAG, "mDialog.onDismiss");
65             }
66         });
67         mDialog.setOnShowListener(new OnShowListener() {
68             @Override
69             public void onShow(DialogInterface dialog) {
70                 if (D.BUG) Log.d(TAG, "mDialog.onShow");
71                 final int h = mDialogView.getHeight();
72                 mDialogView.setTranslationY(-h);
73                 startShowAnimation();
74             }
75         });
76     }
77 
isAnimating()78     public boolean isAnimating() {
79         return mAnimating;
80     }
81 
setShowing(boolean showing)82     private void setShowing(boolean showing) {
83         if (showing == mShowing) return;
84         mShowing = showing;
85         if (D.BUG) Log.d(TAG, "mShowing = " + mShowing);
86         updateAnimating();
87     }
88 
setDismissing(boolean dismissing)89     private void setDismissing(boolean dismissing) {
90         if (dismissing == mDismissing) return;
91         mDismissing = dismissing;
92         if (D.BUG) Log.d(TAG, "mDismissing = " + mDismissing);
93         updateAnimating();
94     }
95 
updateAnimating()96     private void updateAnimating() {
97         final boolean animating = mShowing || mDismissing;
98         if (animating == mAnimating) return;
99         mAnimating = animating;
100         if (D.BUG) Log.d(TAG, "mAnimating = " + mAnimating);
101         if (mCallback != null) {
102             mCallback.onAnimatingChanged(mAnimating);
103         }
104     }
105 
startShow()106     public void startShow() {
107         if (D.BUG) Log.d(TAG, "startShow");
108         if (mShowing) return;
109         setShowing(true);
110         if (mDismissing) {
111             mDialogView.animate().cancel();
112             setDismissing(false);
113             startShowAnimation();
114             return;
115         }
116         if (D.BUG) Log.d(TAG, "mDialog.show()");
117         mDialog.show();
118     }
119 
chevronDistance()120     private int chevronDistance() {
121         return mChevron.getHeight() / 6;
122     }
123 
chevronPosY()124     private int chevronPosY() {
125         final Object tag = mChevron == null ? null : mChevron.getTag();
126         return tag == null ? 0 : (Integer) tag;
127     }
128 
startShowAnimation()129     private void startShowAnimation() {
130         if (D.BUG) Log.d(TAG, "startShowAnimation");
131         mDialogView.animate()
132                 .translationY(0)
133                 .setDuration(scaledDuration(300))
134                 .setInterpolator(new LogDecelerateInterpolator())
135                 .setListener(null)
136                 .setUpdateListener(animation -> {
137                     if (mChevronPositionAnimator != null) {
138                         final float v = (Float) mChevronPositionAnimator.getAnimatedValue();
139                         if (mChevronPositionAnimator == null) return;
140                         // reposition chevron
141                         final int posY = chevronPosY();
142                         mChevron.setTranslationY(posY + v + -mDialogView.getTranslationY());
143                     }
144                 })
145                 .withEndAction(new Runnable() {
146                     @Override
147                     public void run() {
148                         if (mChevronPositionAnimator == null) return;
149                         // reposition chevron
150                         final int posY = chevronPosY();
151                         mChevron.setTranslationY(posY + -mDialogView.getTranslationY());
152                     }
153                 })
154                 .start();
155 
156         mContentsPositionAnimator = ValueAnimator.ofFloat(-chevronDistance(), 0)
157                 .setDuration(scaledDuration(400));
158         mContentsPositionAnimator.addListener(new AnimatorListenerAdapter() {
159             private boolean mCancelled;
160 
161             @Override
162             public void onAnimationEnd(Animator animation) {
163                 if (mCancelled) return;
164                 if (D.BUG) Log.d(TAG, "show.onAnimationEnd");
165                 setShowing(false);
166             }
167             @Override
168             public void onAnimationCancel(Animator animation) {
169                 if (D.BUG) Log.d(TAG, "show.onAnimationCancel");
170                 mCancelled = true;
171             }
172         });
173         mContentsPositionAnimator.addUpdateListener(new AnimatorUpdateListener() {
174             @Override
175             public void onAnimationUpdate(ValueAnimator animation) {
176                 float v = (Float) animation.getAnimatedValue();
177                 mContents.setTranslationY(v + -mDialogView.getTranslationY());
178             }
179         });
180         mContentsPositionAnimator.setInterpolator(new LogDecelerateInterpolator());
181         mContentsPositionAnimator.start();
182 
183         mContents.setAlpha(0);
184         mContents.animate()
185                 .alpha(1)
186                 .setDuration(scaledDuration(150))
187                 .setInterpolator(new PathInterpolator(0f, 0f, .2f, 1f))
188                 .start();
189 
190         mChevronPositionAnimator = ValueAnimator.ofFloat(-chevronDistance(), 0)
191                 .setDuration(scaledDuration(250));
192         mChevronPositionAnimator.setInterpolator(new PathInterpolator(.4f, 0f, .2f, 1f));
193         mChevronPositionAnimator.start();
194 
195         mChevron.setAlpha(0);
196         mChevron.animate()
197                 .alpha(1)
198                 .setStartDelay(scaledDuration(50))
199                 .setDuration(scaledDuration(150))
200                 .setInterpolator(new PathInterpolator(.4f, 0f, 1f, 1f))
201                 .start();
202     }
203 
startDismiss(final Runnable onComplete)204     public void startDismiss(final Runnable onComplete) {
205         if (D.BUG) Log.d(TAG, "startDismiss");
206         if (mDismissing) return;
207         setDismissing(true);
208         if (mShowing) {
209             mDialogView.animate().cancel();
210             if (mContentsPositionAnimator != null) {
211                 mContentsPositionAnimator.cancel();
212             }
213             mContents.animate().cancel();
214             if (mChevronPositionAnimator != null) {
215                 mChevronPositionAnimator.cancel();
216             }
217             mChevron.animate().cancel();
218             setShowing(false);
219         }
220         mDialogView.animate()
221                 .translationY(-mDialogView.getHeight())
222                 .setDuration(scaledDuration(250))
223                 .setInterpolator(new LogAccelerateInterpolator())
224                 .setUpdateListener(new AnimatorUpdateListener() {
225                     @Override
226                     public void onAnimationUpdate(ValueAnimator animation) {
227                         mContents.setTranslationY(-mDialogView.getTranslationY());
228                         final int posY = chevronPosY();
229                         mChevron.setTranslationY(posY + -mDialogView.getTranslationY());
230                     }
231                 })
232                 .setListener(new AnimatorListenerAdapter() {
233                     private boolean mCancelled;
234                     @Override
235                     public void onAnimationEnd(Animator animation) {
236                         if (mCancelled) return;
237                         if (D.BUG) Log.d(TAG, "dismiss.onAnimationEnd");
238                         mHandler.postDelayed(new Runnable() {
239                             @Override
240                             public void run() {
241                                 if (D.BUG) Log.d(TAG, "mDialog.dismiss()");
242                                 mDialog.dismiss();
243                                 onComplete.run();
244                                 setDismissing(false);
245                             }
246                         }, PRE_DISMISS_DELAY);
247 
248                     }
249                     @Override
250                     public void onAnimationCancel(Animator animation) {
251                         if (D.BUG) Log.d(TAG, "dismiss.onAnimationCancel");
252                         mCancelled = true;
253                     }
254                 }).start();
255     }
256 
scaledDuration(int base)257     private static int scaledDuration(int base) {
258         return (int) (base * ANIMATION_SCALE);
259     }
260 
261     public static final class LogDecelerateInterpolator implements TimeInterpolator {
262         private final float mBase;
263         private final float mDrift;
264         private final float mTimeScale;
265         private final float mOutputScale;
266 
LogDecelerateInterpolator()267         public LogDecelerateInterpolator() {
268             this(400f, 1.4f, 0);
269         }
270 
LogDecelerateInterpolator(float base, float timeScale, float drift)271         private LogDecelerateInterpolator(float base, float timeScale, float drift) {
272             mBase = base;
273             mDrift = drift;
274             mTimeScale = 1f / timeScale;
275 
276             mOutputScale = 1f / computeLog(1f);
277         }
278 
computeLog(float t)279         private float computeLog(float t) {
280             return 1f - (float) Math.pow(mBase, -t * mTimeScale) + (mDrift * t);
281         }
282 
283         @Override
getInterpolation(float t)284         public float getInterpolation(float t) {
285             return computeLog(t) * mOutputScale;
286         }
287     }
288 
289     public static final class LogAccelerateInterpolator implements TimeInterpolator {
290         private final int mBase;
291         private final int mDrift;
292         private final float mLogScale;
293 
LogAccelerateInterpolator()294         public LogAccelerateInterpolator() {
295             this(100, 0);
296         }
297 
LogAccelerateInterpolator(int base, int drift)298         private LogAccelerateInterpolator(int base, int drift) {
299             mBase = base;
300             mDrift = drift;
301             mLogScale = 1f / computeLog(1, mBase, mDrift);
302         }
303 
computeLog(float t, int base, int drift)304         private static float computeLog(float t, int base, int drift) {
305             return (float) -Math.pow(base, -t) + 1 + (drift * t);
306         }
307 
308         @Override
getInterpolation(float t)309         public float getInterpolation(float t) {
310             return 1 - computeLog(1 - t, mBase, mDrift) * mLogScale;
311         }
312     }
313 
314     public interface Callback {
onAnimatingChanged(boolean animating)315         void onAnimatingChanged(boolean animating);
316     }
317 }
318