blob: 2df222764dcc382a58eb51a12929f977fb9a3f0a [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.graphics.drawable.Drawable;
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 final Dialog mDialog;
private final View mDialogView;
private final ViewGroup mContents; // volume rows + zen footer
private final View mChevron;
private final Drawable mBackground;
private final Handler mHandler = new Handler();
private final Callback mCallback;
private final int mBackgroundTargetAlpha;
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,
Drawable background, Callback callback) {
mDialog = dialog;
mDialogView = dialogView;
mContents = contents;
mChevron = chevron;
mCallback = callback;
mBackground = background;
mBackgroundTargetAlpha = mBackground.getAlpha();
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 w = mDialogView.getWidth() / 4;
mDialogView.setTranslationX(w);
mBackground.setAlpha(0);
startShowAnimation();
}
});
}
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 0;
}
private int chevronPosY() {
final Object tag = mChevron == null ? null : mChevron.getTag();
return tag == null ? 0 : (Integer) tag;
}
private void startShowAnimation() {
if (D.BUG) Log.d(TAG, "startShowAnimation");
mDialogView.animate()
.translationX(0)
.translationY(0)
.alpha(1)
.setDuration(scaledDuration(300))
.setInterpolator(new LogDecelerateInterpolator())
.setListener(null)
.setUpdateListener(animation -> {
mBackground.setAlpha(
(int) (animation.getAnimatedFraction() * mBackgroundTargetAlpha));
if (mChevronPositionAnimator != null) {
final float v = (Float) mChevronPositionAnimator.getAnimatedValue();
if (mChevronPositionAnimator == null) return;
// reposition chevron
final int posY = chevronPosY();
}
})
.withEndAction(new Runnable() {
@Override
public void run() {
mBackground.setAlpha(mBackgroundTargetAlpha);
if (mChevronPositionAnimator == null) return;
// reposition chevron
final int posY = chevronPosY();
}
})
.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.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();
if (mContentsPositionAnimator != null) {
mContentsPositionAnimator.cancel();
}
mContents.animate().cancel();
if (mChevronPositionAnimator != null) {
mChevronPositionAnimator.cancel();
}
mChevron.animate().cancel();
setShowing(false);
}
mDialogView.animate()
.translationX(mDialogView.getWidth() / 4)
.alpha(0)
.setDuration(scaledDuration(250))
.setInterpolator(new LogAccelerateInterpolator())
.setUpdateListener(animation -> {
final float v = 1 - mChevronPositionAnimator.getAnimatedFraction();
mBackground.setAlpha((int) (v * mBackgroundTargetAlpha));
})
.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(() -> {
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);
}
public static final class LogDecelerateInterpolator implements TimeInterpolator {
private final float mBase;
private final float mDrift;
private final float mTimeScale;
private final float mOutputScale;
public 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;
}
}
public static final class LogAccelerateInterpolator implements TimeInterpolator {
private final int mBase;
private final int mDrift;
private final float mLogScale;
public 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);
}
}