• 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.os.Handler;
29 import android.util.Log;
30 import android.view.View;
31 import android.view.ViewGroup;
32 import android.view.animation.PathInterpolator;
33 
34 public class VolumeDialogMotion {
35     private static final String TAG = Util.logTag(VolumeDialogMotion.class);
36 
37     private static final float ANIMATION_SCALE = 1.0f;
38     private static final int PRE_DISMISS_DELAY = 50;
39 
40     private final Dialog mDialog;
41     private final View mDialogView;
42     private final ViewGroup mContents;  // volume rows + zen footer
43     private final View mChevron;
44     private final Handler mHandler = new Handler();
45     private final Callback mCallback;
46 
47     private boolean mAnimating;  // show or dismiss animation is running
48     private boolean mShowing;  // show animation is running
49     private boolean mDismissing;  // dismiss animation is running
50     private ValueAnimator mChevronPositionAnimator;
51     private ValueAnimator mContentsPositionAnimator;
52 
VolumeDialogMotion(Dialog dialog, View dialogView, ViewGroup contents, View chevron, Callback callback)53     public VolumeDialogMotion(Dialog dialog, View dialogView, ViewGroup contents, View chevron,
54             Callback callback) {
55         mDialog = dialog;
56         mDialogView = dialogView;
57         mContents = contents;
58         mChevron = chevron;
59         mCallback = callback;
60         mDialog.setOnDismissListener(new OnDismissListener() {
61             @Override
62             public void onDismiss(DialogInterface dialog) {
63                 if (D.BUG) Log.d(TAG, "mDialog.onDismiss");
64             }
65         });
66         mDialog.setOnShowListener(new OnShowListener() {
67             @Override
68             public void onShow(DialogInterface dialog) {
69                 if (D.BUG) Log.d(TAG, "mDialog.onShow");
70                 final int h = mDialogView.getHeight();
71                 mDialogView.setTranslationY(-h);
72                 startShowAnimation();
73             }
74         });
75     }
76 
isAnimating()77     public boolean isAnimating() {
78         return mAnimating;
79     }
80 
setShowing(boolean showing)81     private void setShowing(boolean showing) {
82         if (showing == mShowing) return;
83         mShowing = showing;
84         if (D.BUG) Log.d(TAG, "mShowing = " + mShowing);
85         updateAnimating();
86     }
87 
setDismissing(boolean dismissing)88     private void setDismissing(boolean dismissing) {
89         if (dismissing == mDismissing) return;
90         mDismissing = dismissing;
91         if (D.BUG) Log.d(TAG, "mDismissing = " + mDismissing);
92         updateAnimating();
93     }
94 
updateAnimating()95     private void updateAnimating() {
96         final boolean animating = mShowing || mDismissing;
97         if (animating == mAnimating) return;
98         mAnimating = animating;
99         if (D.BUG) Log.d(TAG, "mAnimating = " + mAnimating);
100         if (mCallback != null) {
101             mCallback.onAnimatingChanged(mAnimating);
102         }
103     }
104 
startShow()105     public void startShow() {
106         if (D.BUG) Log.d(TAG, "startShow");
107         if (mShowing) return;
108         setShowing(true);
109         if (mDismissing) {
110             mDialogView.animate().cancel();
111             setDismissing(false);
112             startShowAnimation();
113             return;
114         }
115         if (D.BUG) Log.d(TAG, "mDialog.show()");
116         mDialog.show();
117     }
118 
chevronDistance()119     private int chevronDistance() {
120         return mChevron.getHeight() / 6;
121     }
122 
chevronPosY()123     private int chevronPosY() {
124         final Object tag = mChevron == null ? null : mChevron.getTag();
125         return tag == null ? 0 : (Integer) tag;
126     }
127 
startShowAnimation()128     private void startShowAnimation() {
129         if (D.BUG) Log.d(TAG, "startShowAnimation");
130         mDialogView.animate()
131                 .translationY(0)
132                 .setDuration(scaledDuration(300))
133                 .setInterpolator(new LogDecelerateInterpolator())
134                 .setListener(null)
135                 .setUpdateListener(new AnimatorUpdateListener() {
136                     @Override
137                     public void onAnimationUpdate(ValueAnimator animation) {
138                         if (mChevronPositionAnimator == null) return;
139                         // reposition chevron
140                         final float v = (Float) mChevronPositionAnimator.getAnimatedValue();
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     private 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         private 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     private static final class LogAccelerateInterpolator implements TimeInterpolator {
290         private final int mBase;
291         private final int mDrift;
292         private final float mLogScale;
293 
LogAccelerateInterpolator()294         private 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