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                 })
    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 
    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 
    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 
    267         private LogDecelerateInterpolator() {
    268             this(400f, 1.4f, 0);
    269         }
    270 
    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 
    279         private float computeLog(float t) {
    280             return 1f - (float) Math.pow(mBase, -t * mTimeScale) + (mDrift * t);
    281         }
    282 
    283         @Override
    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 
    294         private LogAccelerateInterpolator() {
    295             this(100, 0);
    296         }
    297 
    298         private LogAccelerateInterpolator(int base, int drift) {
    299             mBase = base;
    300             mDrift = drift;
    301             mLogScale = 1f / computeLog(1, mBase, mDrift);
    302         }
    303 
    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
    309         public float getInterpolation(float t) {
    310             return 1 - computeLog(1 - t, mBase, mDrift) * mLogScale;
    311         }
    312     }
    313 
    314     public interface Callback {
    315         void onAnimatingChanged(boolean animating);
    316     }
    317 }
    318