blob: fdf18406de94c733177785f1e0875f1fba467a14 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.volume;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnDismissListener;
import android.content.DialogInterface.OnShowListener;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.PathInterpolator;
public class VolumeDialogMotion {
private static final String TAG = Util.logTag(VolumeDialogMotion.class);
private static final float ANIMATION_SCALE = 1.0f;
private static final int PRE_DISMISS_DELAY = 50;
private static final int POST_SHOW_DELAY = 200;
private final Dialog mDialog;
private final View mDialogView;
private final ViewGroup mContents; // volume rows + zen footer
private final View mChevron;
private final Handler mHandler = new Handler();
private final Callback mCallback;
private boolean mAnimating; // show or dismiss animation is running
private boolean mShowing; // show animation is running
private boolean mDismissing; // dismiss animation is running
private ValueAnimator mChevronPositionAnimator;
private ValueAnimator mContentsPositionAnimator;
public VolumeDialogMotion(Dialog dialog, View dialogView, ViewGroup contents, View chevron,
Callback callback) {
mDialog = dialog;
mDialogView = dialogView;
mContents = contents;
mChevron = chevron;
mCallback = callback;
mDialog.setOnDismissListener(new OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
if (D.BUG) Log.d(TAG, "mDialog.onDismiss");
}
});
mDialog.setOnShowListener(new OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
if (D.BUG) Log.d(TAG, "mDialog.onShow");
final int h = mDialogView.getHeight();
mDialogView.setTranslationY(-h);
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
startShowAnimation();
}
}, POST_SHOW_DELAY);
}
});
}
public boolean isAnimating() {
return mAnimating;
}
private void setShowing(boolean showing) {
if (showing == mShowing) return;
mShowing = showing;
if (D.BUG) Log.d(TAG, "mShowing = " + mShowing);
updateAnimating();
}
private void setDismissing(boolean dismissing) {
if (dismissing == mDismissing) return;
mDismissing = dismissing;
if (D.BUG) Log.d(TAG, "mDismissing = " + mDismissing);
updateAnimating();
}
private void updateAnimating() {
final boolean animating = mShowing || mDismissing;
if (animating == mAnimating) return;
mAnimating = animating;
if (D.BUG) Log.d(TAG, "mAnimating = " + mAnimating);
if (mCallback != null) {
mCallback.onAnimatingChanged(mAnimating);
}
}
public void startShow() {
if (D.BUG) Log.d(TAG, "startShow");
if (mShowing) return;
setShowing(true);
if (mDismissing) {
mDialogView.animate().cancel();
setDismissing(false);
startShowAnimation();
return;
}
if (D.BUG) Log.d(TAG, "mDialog.show()");
mDialog.show();
}
private int chevronDistance() {
return mChevron.getHeight() / 6;
}
private void startShowAnimation() {
if (D.BUG) Log.d(TAG, "startShowAnimation");
mDialogView.animate()
.translationY(0)
.setDuration(scaledDuration(300))
.setInterpolator(new LogDecelerateInterpolator())
.setListener(null)
.setUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
if (mChevronPositionAnimator == null) return;
// reposition chevron
final float v = (Float) mChevronPositionAnimator.getAnimatedValue();
final int posY = (Integer) mChevron.getTag();
mChevron.setTranslationY(posY + v + -mDialogView.getTranslationY());
}})
.start();
mContentsPositionAnimator = ValueAnimator.ofFloat(-chevronDistance(), 0)
.setDuration(scaledDuration(400));
mContentsPositionAnimator.addListener(new AnimatorListenerAdapter() {
private boolean mCancelled;
@Override
public void onAnimationEnd(Animator animation) {
if (mCancelled) return;
if (D.BUG) Log.d(TAG, "show.onAnimationEnd");
setShowing(false);
}
@Override
public void onAnimationCancel(Animator animation) {
if (D.BUG) Log.d(TAG, "show.onAnimationCancel");
mCancelled = true;
}
});
mContentsPositionAnimator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float v = (Float) animation.getAnimatedValue();
mContents.setTranslationY(v + -mDialogView.getTranslationY());
}
});
mContentsPositionAnimator.setInterpolator(new LogDecelerateInterpolator());
mContentsPositionAnimator.start();
mContents.setAlpha(0);
mContents.animate()
.alpha(1)
.setDuration(scaledDuration(150))
.setInterpolator(new PathInterpolator(0f, 0f, .2f, 1f))
.start();
mChevronPositionAnimator = ValueAnimator.ofFloat(-chevronDistance(), 0)
.setDuration(scaledDuration(250));
mChevronPositionAnimator.setInterpolator(new PathInterpolator(.4f, 0f, .2f, 1f));
mChevronPositionAnimator.start();
mChevron.setAlpha(0);
mChevron.animate()
.alpha(1)
.setStartDelay(scaledDuration(50))
.setDuration(scaledDuration(150))
.setInterpolator(new PathInterpolator(.4f, 0f, 1f, 1f))
.start();
}
public void startDismiss(final Runnable onComplete) {
if (D.BUG) Log.d(TAG, "startDismiss");
if (mDismissing) return;
setDismissing(true);
if (mShowing) {
mDialogView.animate().cancel();
mContentsPositionAnimator.cancel();
mContents.animate().cancel();
mChevronPositionAnimator.cancel();
mChevron.animate().cancel();
setShowing(false);
}
mDialogView.animate()
.translationY(-mDialogView.getHeight())
.setDuration(scaledDuration(250))
.setInterpolator(new LogAccelerateInterpolator())
.setUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mContents.setTranslationY(-mDialogView.getTranslationY());
int posY = (Integer) mChevron.getTag();
mChevron.setTranslationY(posY + -mDialogView.getTranslationY());
}
})
.setListener(new AnimatorListenerAdapter() {
private boolean mCancelled;
@Override
public void onAnimationEnd(Animator animation) {
if (mCancelled) return;
if (D.BUG) Log.d(TAG, "dismiss.onAnimationEnd");
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (D.BUG) Log.d(TAG, "mDialog.dismiss()");
mDialog.dismiss();
onComplete.run();
setDismissing(false);
}
}, PRE_DISMISS_DELAY);
}
@Override
public void onAnimationCancel(Animator animation) {
if (D.BUG) Log.d(TAG, "dismiss.onAnimationCancel");
mCancelled = true;
}
}).start();
}
private static int scaledDuration(int base) {
return (int) (base * ANIMATION_SCALE);
}
private static final class LogDecelerateInterpolator implements TimeInterpolator {
private final float mBase;
private final float mDrift;
private final float mTimeScale;
private final float mOutputScale;
private LogDecelerateInterpolator() {
this(400f, 1.4f, 0);
}
private LogDecelerateInterpolator(float base, float timeScale, float drift) {
mBase = base;
mDrift = drift;
mTimeScale = 1f / timeScale;
mOutputScale = 1f / computeLog(1f);
}
private float computeLog(float t) {
return 1f - (float) Math.pow(mBase, -t * mTimeScale) + (mDrift * t);
}
@Override
public float getInterpolation(float t) {
return computeLog(t) * mOutputScale;
}
}
private static final class LogAccelerateInterpolator implements TimeInterpolator {
private final int mBase;
private final int mDrift;
private final float mLogScale;
private LogAccelerateInterpolator() {
this(100, 0);
}
private LogAccelerateInterpolator(int base, int drift) {
mBase = base;
mDrift = drift;
mLogScale = 1f / computeLog(1, mBase, mDrift);
}
private static float computeLog(float t, int base, int drift) {
return (float) -Math.pow(base, -t) + 1 + (drift * t);
}
@Override
public float getInterpolation(float t) {
return 1 - computeLog(1 - t, mBase, mDrift) * mLogScale;
}
}
public interface Callback {
void onAnimatingChanged(boolean animating);
}
}