Home | History | Annotate | Download | only in volume
      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 
     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 
     77     public boolean isAnimating() {
     78         return mAnimating;
     79     }
     80 
     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 
     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 
     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 
    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 
    119     private int chevronDistance() {
    120         return mChevron.getHeight() / 6;
    121     }
    122 
    123     private int chevronPosY() {
    124         final Object tag = mChevron == null ? null : mChevron.getTag();
    125         return tag == null ? 0 : (Integer) tag;
    126     }
    127 
    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                 .start();
    145 
    146         mContentsPositionAnimator = ValueAnimator.ofFloat(-chevronDistance(), 0)
    147                 .setDuration(scaledDuration(400));
    148         mContentsPositionAnimator.addListener(new AnimatorListenerAdapter() {
    149             private boolean mCancelled;
    150 
    151             @Override
    152             public void onAnimationEnd(Animator animation) {
    153                 if (mCancelled) return;
    154                 if (D.BUG) Log.d(TAG, "show.onAnimationEnd");
    155                 setShowing(false);
    156             }
    157             @Override
    158             public void onAnimationCancel(Animator animation) {
    159                 if (D.BUG) Log.d(TAG, "show.onAnimationCancel");
    160                 mCancelled = true;
    161             }
    162         });
    163         mContentsPositionAnimator.addUpdateListener(new AnimatorUpdateListener() {
    164             @Override
    165             public void onAnimationUpdate(ValueAnimator animation) {
    166                 float v = (Float) animation.getAnimatedValue();
    167                 mContents.setTranslationY(v + -mDialogView.getTranslationY());
    168             }
    169         });
    170         mContentsPositionAnimator.setInterpolator(new LogDecelerateInterpolator());
    171         mContentsPositionAnimator.start();
    172 
    173         mContents.setAlpha(0);
    174         mContents.animate()
    175                 .alpha(1)
    176                 .setDuration(scaledDuration(150))
    177                 .setInterpolator(new PathInterpolator(0f, 0f, .2f, 1f))
    178                 .start();
    179 
    180         mChevronPositionAnimator = ValueAnimator.ofFloat(-chevronDistance(), 0)
    181                 .setDuration(scaledDuration(250));
    182         mChevronPositionAnimator.setInterpolator(new PathInterpolator(.4f, 0f, .2f, 1f));
    183         mChevronPositionAnimator.start();
    184 
    185         mChevron.setAlpha(0);
    186         mChevron.animate()
    187                 .alpha(1)
    188                 .setStartDelay(scaledDuration(50))
    189                 .setDuration(scaledDuration(150))
    190                 .setInterpolator(new PathInterpolator(.4f, 0f, 1f, 1f))
    191                 .start();
    192     }
    193 
    194     public void startDismiss(final Runnable onComplete) {
    195         if (D.BUG) Log.d(TAG, "startDismiss");
    196         if (mDismissing) return;
    197         setDismissing(true);
    198         if (mShowing) {
    199             mDialogView.animate().cancel();
    200             if (mContentsPositionAnimator != null) {
    201                 mContentsPositionAnimator.cancel();
    202             }
    203             mContents.animate().cancel();
    204             if (mChevronPositionAnimator != null) {
    205                 mChevronPositionAnimator.cancel();
    206             }
    207             mChevron.animate().cancel();
    208             setShowing(false);
    209         }
    210         mDialogView.animate()
    211                 .translationY(-mDialogView.getHeight())
    212                 .setDuration(scaledDuration(250))
    213                 .setInterpolator(new LogAccelerateInterpolator())
    214                 .setUpdateListener(new AnimatorUpdateListener() {
    215                     @Override
    216                     public void onAnimationUpdate(ValueAnimator animation) {
    217                         mContents.setTranslationY(-mDialogView.getTranslationY());
    218                         final int posY = chevronPosY();
    219                         mChevron.setTranslationY(posY + -mDialogView.getTranslationY());
    220                     }
    221                 })
    222                 .setListener(new AnimatorListenerAdapter() {
    223                     private boolean mCancelled;
    224                     @Override
    225                     public void onAnimationEnd(Animator animation) {
    226                         if (mCancelled) return;
    227                         if (D.BUG) Log.d(TAG, "dismiss.onAnimationEnd");
    228                         mHandler.postDelayed(new Runnable() {
    229                             @Override
    230                             public void run() {
    231                                 if (D.BUG) Log.d(TAG, "mDialog.dismiss()");
    232                                 mDialog.dismiss();
    233                                 onComplete.run();
    234                                 setDismissing(false);
    235                             }
    236                         }, PRE_DISMISS_DELAY);
    237 
    238                     }
    239                     @Override
    240                     public void onAnimationCancel(Animator animation) {
    241                         if (D.BUG) Log.d(TAG, "dismiss.onAnimationCancel");
    242                         mCancelled = true;
    243                     }
    244                 }).start();
    245     }
    246 
    247     private static int scaledDuration(int base) {
    248         return (int) (base * ANIMATION_SCALE);
    249     }
    250 
    251     private static final class LogDecelerateInterpolator implements TimeInterpolator {
    252         private final float mBase;
    253         private final float mDrift;
    254         private final float mTimeScale;
    255         private final float mOutputScale;
    256 
    257         private LogDecelerateInterpolator() {
    258             this(400f, 1.4f, 0);
    259         }
    260 
    261         private LogDecelerateInterpolator(float base, float timeScale, float drift) {
    262             mBase = base;
    263             mDrift = drift;
    264             mTimeScale = 1f / timeScale;
    265 
    266             mOutputScale = 1f / computeLog(1f);
    267         }
    268 
    269         private float computeLog(float t) {
    270             return 1f - (float) Math.pow(mBase, -t * mTimeScale) + (mDrift * t);
    271         }
    272 
    273         @Override
    274         public float getInterpolation(float t) {
    275             return computeLog(t) * mOutputScale;
    276         }
    277     }
    278 
    279     private static final class LogAccelerateInterpolator implements TimeInterpolator {
    280         private final int mBase;
    281         private final int mDrift;
    282         private final float mLogScale;
    283 
    284         private LogAccelerateInterpolator() {
    285             this(100, 0);
    286         }
    287 
    288         private LogAccelerateInterpolator(int base, int drift) {
    289             mBase = base;
    290             mDrift = drift;
    291             mLogScale = 1f / computeLog(1, mBase, mDrift);
    292         }
    293 
    294         private static float computeLog(float t, int base, int drift) {
    295             return (float) -Math.pow(base, -t) + 1 + (drift * t);
    296         }
    297 
    298         @Override
    299         public float getInterpolation(float t) {
    300             return 1 - computeLog(1 - t, mBase, mDrift) * mLogScale;
    301         }
    302     }
    303 
    304     public interface Callback {
    305         void onAnimatingChanged(boolean animating);
    306     }
    307 }
    308