Merge "Volume Motion: Initial show and expand transition." into mnc-dev

This commit is contained in:
John Spurlock
2015-06-24 18:46:51 +00:00
committed by Android (Google) Code Review
7 changed files with 412 additions and 25 deletions

View File

@@ -18,7 +18,7 @@
android:id="@+id/volume_dialog"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:layout_marginBottom="@dimen/volume_dialog_margin_bottom"
android:layout_marginLeft="@dimen/notification_side_padding"
android:layout_marginRight="@dimen/notification_side_padding"
android:background="@drawable/volume_dialog_background"

View File

@@ -17,6 +17,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:id="@+id/volume_dialog_row"
android:paddingEnd="8dp"
android:paddingStart="8dp" >

View File

@@ -576,6 +576,9 @@
<!-- Standard image button size for volume dialog buttons -->
<dimen name="volume_button_size">48dp</dimen>
<!-- Volume dialog root view bottom margin, at rest -->
<dimen name="volume_dialog_margin_bottom">4dp</dimen>
<!-- Padding between icon and text for managed profile toast -->
<dimen name="managed_profile_toast_padding">4dp</dimen>

View File

@@ -291,11 +291,6 @@
<item name="android:textColor">#ffb0b3c5</item>
</style>
<style name="VolumeDialogAnimations">
<item name="android:windowEnterAnimation">@android:anim/fade_in</item>
<item name="android:windowExitAnimation">@android:anim/fade_out</item>
</style>
<style name="VolumeButtons" parent="@android:style/Widget.Material.Button.Borderless">
<item name="android:background">@drawable/btn_borderless_rect</item>
</style>

View File

@@ -111,6 +111,7 @@ public class VolumeDialog {
private final Accessibility mAccessibility = new Accessibility();
private final ColorStateList mActiveSliderTint;
private final ColorStateList mInactiveSliderTint;
private final VolumeDialogMotion mMotion;
private boolean mShowing;
private boolean mExpanded;
@@ -120,9 +121,12 @@ public class VolumeDialog {
private boolean mSilentMode = VolumePrefs.DEFAULT_ENABLE_SILENT_MODE;
private State mState;
private int mExpandButtonRes;
private boolean mExpanding;
private boolean mExpandButtonAnimationRunning;
private SafetyWarningDialog mSafetyWarning;
private Callback mCallback;
private boolean mPendingStateChanged;
private boolean mPendingRecheckAll;
private long mCollapseTime;
public VolumeDialog(Context context, int windowType, VolumeDialogController controller,
ZenModeController zenModeController, Callback callback) {
@@ -151,7 +155,6 @@ public class VolumeDialog {
lp.format = PixelFormat.TRANSLUCENT;
lp.setTitle(VolumeDialog.class.getSimpleName());
lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
lp.windowAnimations = R.style.VolumeDialogAnimations;
lp.y = res.getDimensionPixelSize(R.dimen.volume_offset_top);
lp.gravity = Gravity.TOP;
window.setAttributes(lp);
@@ -168,9 +171,22 @@ public class VolumeDialog {
updateExpandButtonH();
mLayoutTransition = new LayoutTransition();
mLayoutTransition.setDuration(new ValueAnimator().getDuration() / 2);
mLayoutTransition.disableTransitionType(LayoutTransition.DISAPPEARING);
mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
mDialogContentView.setLayoutTransition(mLayoutTransition);
mMotion = new VolumeDialogMotion(mDialog, mDialogView, mDialogContentView, mExpandButton,
new VolumeDialogMotion.Callback() {
@Override
public void onAnimatingChanged(boolean animating) {
if (animating) return;
if (mPendingStateChanged) {
mHandler.sendEmptyMessage(H.STATE_CHANGED);
mPendingStateChanged = false;
}
if (mPendingRecheckAll) {
mHandler.sendEmptyMessage(H.RECHECK_ALL);
mPendingRecheckAll = false;
}
}
});
addRow(AudioManager.STREAM_RING,
R.drawable.ic_volume_ringer, R.drawable.ic_volume_ringer_mute, true);
@@ -242,6 +258,7 @@ public class VolumeDialog {
final VolumeRow row = initRow(stream, iconRes, iconMuteRes, important);
if (!mRows.isEmpty()) {
final View v = new View(mContext);
v.setId(android.R.id.background);
final int h = mContext.getResources()
.getDimensionPixelSize(R.dimen.volume_slider_interspacing);
final LinearLayout.LayoutParams lp =
@@ -253,10 +270,11 @@ public class VolumeDialog {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
if (D.BUG) Log.d(TAG, "onLayoutChange"
final boolean moved = oldLeft != left || oldTop != top;
if (D.BUG) Log.d(TAG, "onLayoutChange moved=" + moved
+ " old=" + new Rect(oldLeft, oldTop, oldRight, oldBottom).toShortString()
+ " new=" + new Rect(left,top,right,bottom).toShortString());
if (oldLeft != left || oldTop != top) {
if (moved) {
for (int i = 0; i < mDialogContentView.getChildCount(); i++) {
final View c = mDialogContentView.getChildAt(i);
if (!c.isShown()) continue;
@@ -302,18 +320,21 @@ public class VolumeDialog {
if (D.BUG) Log.d(TAG, "repositionExpandAnim x=" + x + " y=" + y);
mExpandButton.setTranslationX(x);
mExpandButton.setTranslationY(y);
mExpandButton.setTag((Integer) y);
}
public void dump(PrintWriter writer) {
writer.println(VolumeDialog.class.getSimpleName() + " state:");
writer.print(" mShowing: "); writer.println(mShowing);
writer.print(" mExpanded: "); writer.println(mExpanded);
writer.print(" mExpanding: "); writer.println(mExpanding);
writer.print(" mExpandButtonAnimationRunning: ");
writer.println(mExpandButtonAnimationRunning);
writer.print(" mActiveStream: "); writer.println(mActiveStream);
writer.print(" mDynamic: "); writer.println(mDynamic);
writer.print(" mShowHeaders: "); writer.println(mShowHeaders);
writer.print(" mAutomute: "); writer.println(mAutomute);
writer.print(" mSilentMode: "); writer.println(mSilentMode);
writer.print(" mCollapseTime: "); writer.println(mCollapseTime);
writer.print(" mAccessibility.mFeedbackEnabled: ");
writer.println(mAccessibility.mFeedbackEnabled);
}
@@ -412,12 +433,13 @@ public class VolumeDialog {
}
private void showH(int reason) {
if (D.BUG) Log.d(TAG, "showH r=" + Events.DISMISS_REASONS[reason]);
mHandler.removeMessages(H.SHOW);
mHandler.removeMessages(H.DISMISS);
rescheduleTimeoutH();
if (mShowing) return;
mShowing = true;
mDialog.show();
mMotion.startShow();
Events.writeEvent(mContext, Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked());
mController.notifyVisible(true);
}
@@ -434,7 +456,7 @@ public class VolumeDialog {
private int computeTimeoutH() {
if (mAccessibility.mFeedbackEnabled) return 20000;
if (mSafetyWarning != null) return 5000;
if (mExpanded || mExpanding) return 5000;
if (mExpanded || mExpandButtonAnimationRunning) return 5000;
if (mActiveStream == AudioManager.STREAM_MUSIC) return 1500;
return 3000;
}
@@ -444,9 +466,13 @@ public class VolumeDialog {
mHandler.removeMessages(H.SHOW);
if (!mShowing) return;
mShowing = false;
mDialog.dismiss();
mMotion.startDismiss(new Runnable() {
@Override
public void run() {
setExpandedH(false);
}
});
Events.writeEvent(mContext, Events.EVENT_DISMISS_DIALOG, reason);
setExpandedH(false);
mController.notifyVisible(false);
synchronized (mSafetyWarningLock) {
if (mSafetyWarning != null) {
@@ -456,13 +482,40 @@ public class VolumeDialog {
}
}
private void updateDialogBottomMarginH() {
final long diff = System.currentTimeMillis() - mCollapseTime;
final boolean collapsing = mCollapseTime != 0 && diff < getConservativeCollapseDuration();
final ViewGroup.MarginLayoutParams mlp = (MarginLayoutParams) mDialogView.getLayoutParams();
final int bottomMargin = collapsing ? mDialogContentView.getHeight() :
mContext.getResources().getDimensionPixelSize(R.dimen.volume_dialog_margin_bottom);
if (bottomMargin != mlp.bottomMargin) {
if (D.BUG) Log.d(TAG, "bottomMargin " + mlp.bottomMargin + " -> " + bottomMargin);
mlp.bottomMargin = bottomMargin;
mDialogView.setLayoutParams(mlp);
}
}
private long getConservativeCollapseDuration() {
return mExpandButtonAnimationDuration * 3;
}
private void prepareForCollapse() {
mHandler.removeMessages(H.UPDATE_BOTTOM_MARGIN);
mCollapseTime = System.currentTimeMillis();
updateDialogBottomMarginH();
mHandler.sendEmptyMessageDelayed(H.UPDATE_BOTTOM_MARGIN, getConservativeCollapseDuration());
}
private void setExpandedH(boolean expanded) {
if (mExpanded == expanded) return;
mExpanded = expanded;
mExpanding = isAttached();
mExpandButtonAnimationRunning = isAttached();
if (D.BUG) Log.d(TAG, "setExpandedH " + expanded);
if (!mExpanded && mExpandButtonAnimationRunning) {
prepareForCollapse();
}
updateRowsH();
if (mExpanding) {
if (mExpandButtonAnimationRunning) {
final Drawable d = mExpandButton.getDrawable();
if (d instanceof AnimatedVectorDrawable) {
// workaround to reset drawable
@@ -473,7 +526,7 @@ public class VolumeDialog {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mExpanding = false;
mExpandButtonAnimationRunning = false;
updateExpandButtonH();
rescheduleTimeoutH();
}
@@ -484,8 +537,9 @@ public class VolumeDialog {
}
private void updateExpandButtonH() {
mExpandButton.setClickable(!mExpanding);
if (mExpanding && isAttached()) return;
if (D.BUG) Log.d(TAG, "updateExpandButtonH");
mExpandButton.setClickable(!mExpandButtonAnimationRunning);
if (mExpandButtonAnimationRunning && isAttached()) return;
final int res = mExpanded ? R.drawable.ic_volume_collapse_animation
: R.drawable.ic_volume_expand_animation;
if (res == mExpandButtonRes) return;
@@ -502,6 +556,7 @@ public class VolumeDialog {
}
private void updateRowsH() {
if (D.BUG) Log.d(TAG, "updateRowsH");
final VolumeRow activeRow = getActiveRow();
updateFooterH();
updateExpandButtonH();
@@ -531,6 +586,7 @@ public class VolumeDialog {
}
private void trimObsoleteH() {
if (D.BUG) Log.d(TAG, "trimObsoleteH");
for (int i = mRows.size() -1; i >= 0; i--) {
final VolumeRow row = mRows.get(i);
if (row.ss == null || !row.ss.dynamic) continue;
@@ -543,7 +599,13 @@ public class VolumeDialog {
}
private void onStateChangedH(State state) {
final boolean animating = mMotion.isAnimating();
if (D.BUG) Log.d(TAG, "onStateChangedH animating=" + animating);
mState = state;
if (animating) {
mPendingStateChanged = true;
return;
}
mDynamic.clear();
// add any new dynamic rows
for (int i = 0; i < state.states.size(); i++) {
@@ -568,11 +630,18 @@ public class VolumeDialog {
}
private void updateFooterH() {
Util.setVisOrGone(mZenFooter, mState.zenMode != Global.ZEN_MODE_OFF);
if (D.BUG) Log.d(TAG, "updateFooterH");
final boolean wasVisible = mZenFooter.getVisibility() == View.VISIBLE;
final boolean visible = mState.zenMode != Global.ZEN_MODE_OFF;
if (wasVisible != visible && !visible) {
prepareForCollapse();
}
Util.setVisOrGone(mZenFooter, visible);
mZenFooter.update();
}
private void updateVolumeRowH(VolumeRow row) {
if (D.BUG) Log.d(TAG, "updateVolumeRowH s=" + row.stream);
if (mState == null) return;
final StreamState ss = mState.states.get(row.stream);
if (ss == null) return;
@@ -841,7 +910,7 @@ public class VolumeDialog {
private final OnClickListener mClickExpand = new OnClickListener() {
@Override
public void onClick(View v) {
if (mExpanding) return;
if (mExpandButtonAnimationRunning) return;
final boolean newExpand = !mExpanded;
Events.writeEvent(mContext, Events.EVENT_EXPAND, newExpand);
setExpandedH(newExpand);
@@ -870,6 +939,8 @@ public class VolumeDialog {
private static final int RECHECK_ALL = 4;
private static final int SET_STREAM_IMPORTANT = 5;
private static final int RESCHEDULE_TIMEOUT = 6;
private static final int STATE_CHANGED = 7;
private static final int UPDATE_BOTTOM_MARGIN = 8;
public H() {
super(Looper.getMainLooper());
@@ -884,6 +955,8 @@ public class VolumeDialog {
case RECHECK_ALL: recheckH(null); break;
case SET_STREAM_IMPORTANT: setStreamImportantH(msg.arg1, msg.arg2 != 0); break;
case RESCHEDULE_TIMEOUT: rescheduleTimeoutH(); break;
case STATE_CHANGED: onStateChangedH(mState); break;
case UPDATE_BOTTOM_MARGIN: updateDialogBottomMarginH(); break;
}
}
}
@@ -902,6 +975,12 @@ public class VolumeDialog {
@Override
protected void onStop() {
super.onStop();
final boolean animating = mMotion.isAnimating();
if (D.BUG) Log.d(TAG, "onStop animating=" + animating);
if (animating) {
mPendingRecheckAll = true;
return;
}
mHandler.sendEmptyMessage(H.RECHECK_ALL);
}
@@ -978,11 +1057,13 @@ public class VolumeDialog {
mDialogView.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
@Override
public void onViewDetachedFromWindow(View v) {
if (D.BUG) Log.d(TAG, "onViewDetachedFromWindow");
// noop
}
@Override
public void onViewAttachedToWindow(View v) {
if (D.BUG) Log.d(TAG, "onViewAttachedToWindow");
updateFeedbackEnabled();
}
});

View File

@@ -0,0 +1,304 @@
/*
* 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);
}
}

View File

@@ -16,6 +16,7 @@
package com.android.systemui.volume;
import android.animation.LayoutTransition;
import android.animation.ValueAnimator;
import android.content.Context;
import android.provider.Settings.Global;
import android.service.notification.ZenModeConfig;
@@ -51,7 +52,9 @@ public class ZenFooter extends LinearLayout {
super(context, attrs);
mContext = context;
mSpTexts = new SpTexts(mContext);
setLayoutTransition(new LayoutTransition());
final LayoutTransition layoutTransition = new LayoutTransition();
layoutTransition.setDuration(new ValueAnimator().getDuration() / 2);
setLayoutTransition(layoutTransition);
}
@Override