Fix janky wakeup animation
Previous wake-up animation was totally broken before, as multiple animations were running with different interpolators and different durations, creating a really jaring experience for the front-door of the phone, which users see hundreds of times a day. Instead, we use a single animator to drive everything, and pass in both the interpolated and the linear value so the right value can be used in whichever place. Test: Wakeup/sleep phone with different numbers of notifications Fixes: 110980608 Change-Id: If1758404a4c49fcd7dc9fa3c93bb18e42a586632
This commit is contained in:
@@ -31,6 +31,13 @@ import com.android.systemui.statusbar.stack.HeadsUpAppearInterpolator;
|
||||
*/
|
||||
public class Interpolators {
|
||||
public static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
|
||||
|
||||
/**
|
||||
* Like {@link #FAST_OUT_SLOW_IN}, but used in case the animation is played in reverse (i.e. t
|
||||
* goes from 1 to 0 instead of 0 to 1).
|
||||
*/
|
||||
public static final Interpolator FAST_OUT_SLOW_IN_REVERSE =
|
||||
new PathInterpolator(0.8f, 0f, 0.6f, 1f);
|
||||
public static final Interpolator FAST_OUT_LINEAR_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
|
||||
public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f);
|
||||
public static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
|
||||
@@ -51,4 +58,11 @@ public class Interpolators {
|
||||
*/
|
||||
public static final Interpolator TOUCH_RESPONSE =
|
||||
new PathInterpolator(0.3f, 0f, 0.1f, 1f);
|
||||
|
||||
/**
|
||||
* Like {@link #TOUCH_RESPONSE}, but used in case the animation is played in reverse (i.e. t
|
||||
* goes from 1 to 0 instead of 0 to 1).
|
||||
*/
|
||||
public static final Interpolator TOUCH_RESPONSE_REVERSE =
|
||||
new PathInterpolator(0.9f, 0f, 0.7f, 1f);
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import com.android.internal.logging.MetricsLogger;
|
||||
@@ -107,17 +108,20 @@ public class NotificationPanelView extends PanelView implements
|
||||
private static final AnimationProperties CLOCK_ANIMATION_PROPERTIES = new AnimationProperties()
|
||||
.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
|
||||
private static final FloatProperty<NotificationPanelView> SET_DARK_AMOUNT_PROPERTY =
|
||||
new FloatProperty<NotificationPanelView>("mDarkAmount") {
|
||||
new FloatProperty<NotificationPanelView>("mInterpolatedDarkAmount") {
|
||||
|
||||
@Override
|
||||
public void setValue(NotificationPanelView object, float value) {
|
||||
object.setDarkAmount(value);
|
||||
object.setDarkAmount(value, object.mDarkInterpolator.getInterpolation(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float get(NotificationPanelView object) {
|
||||
return object.mDarkAmount;
|
||||
return object.mLinearDarkAmount;
|
||||
}
|
||||
};
|
||||
|
||||
private Interpolator mDarkInterpolator;
|
||||
private final PowerManager mPowerManager;
|
||||
private final AccessibilityManager mAccessibilityManager;
|
||||
|
||||
@@ -237,7 +241,18 @@ public class NotificationPanelView extends PanelView implements
|
||||
private int mIndicationBottomPadding;
|
||||
private int mAmbientIndicationBottomPadding;
|
||||
private boolean mIsFullWidth;
|
||||
private float mDarkAmount;
|
||||
|
||||
/**
|
||||
* Current dark amount that follows regular interpolation curve of animation.
|
||||
*/
|
||||
private float mInterpolatedDarkAmount;
|
||||
|
||||
/**
|
||||
* Dark amount that animates from 0 to 1 or vice-versa in linear manner, even if the
|
||||
* interpolation curve is different.
|
||||
*/
|
||||
private float mLinearDarkAmount;
|
||||
|
||||
private float mDarkAmountTarget;
|
||||
private boolean mPulsing;
|
||||
private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
|
||||
@@ -392,7 +407,7 @@ public class NotificationPanelView extends PanelView implements
|
||||
false);
|
||||
addView(mKeyguardBottomArea, index);
|
||||
initBottomArea();
|
||||
setDarkAmount(mDarkAmount);
|
||||
setDarkAmount(mLinearDarkAmount, mInterpolatedDarkAmount);
|
||||
|
||||
setKeyguardStatusViewVisibility(mStatusBarState, false, false);
|
||||
setKeyguardBottomAreaVisibility(mStatusBarState, false);
|
||||
@@ -506,7 +521,7 @@ public class NotificationPanelView extends PanelView implements
|
||||
getExpandedFraction(),
|
||||
totalHeight,
|
||||
mKeyguardStatusView.getHeight(),
|
||||
mDarkAmount,
|
||||
mInterpolatedDarkAmount,
|
||||
mStatusBar.isKeyguardCurrentlySecure(),
|
||||
mPulsing,
|
||||
mBouncerTop);
|
||||
@@ -1917,7 +1932,7 @@ public class NotificationPanelView extends PanelView implements
|
||||
if (view == null && mQsExpanded) {
|
||||
return;
|
||||
}
|
||||
if (needsAnimation && mDarkAmount == 0) {
|
||||
if (needsAnimation && mInterpolatedDarkAmount == 0) {
|
||||
mAnimateNextPositionUpdate = true;
|
||||
}
|
||||
ExpandableView firstChildNotGone = mNotificationStackScroller.getFirstChildNotGone();
|
||||
@@ -2727,20 +2742,28 @@ public class NotificationPanelView extends PanelView implements
|
||||
}
|
||||
mDarkAmountTarget = darkAmount;
|
||||
if (animate) {
|
||||
if (mInterpolatedDarkAmount == 0f || mInterpolatedDarkAmount == 1f) {
|
||||
mDarkInterpolator = dozing
|
||||
? Interpolators.FAST_OUT_SLOW_IN
|
||||
: Interpolators.TOUCH_RESPONSE_REVERSE;
|
||||
}
|
||||
mNotificationStackScroller.notifyDarkAnimationStart(dozing);
|
||||
mDarkAnimator = ObjectAnimator.ofFloat(this, SET_DARK_AMOUNT_PROPERTY, darkAmount);
|
||||
mDarkAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
|
||||
mDarkAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_WAKEUP);
|
||||
mDarkAnimator.setInterpolator(Interpolators.LINEAR);
|
||||
mDarkAnimator.setDuration(mNotificationStackScroller.getDarkAnimationDuration(dozing));
|
||||
mDarkAnimator.start();
|
||||
} else {
|
||||
setDarkAmount(darkAmount);
|
||||
setDarkAmount(darkAmount, darkAmount);
|
||||
}
|
||||
}
|
||||
|
||||
private void setDarkAmount(float amount) {
|
||||
mDarkAmount = amount;
|
||||
mKeyguardStatusView.setDarkAmount(mDarkAmount);
|
||||
mKeyguardBottomArea.setDarkAmount(mDarkAmount);
|
||||
private void setDarkAmount(float linearAmount, float amount) {
|
||||
mInterpolatedDarkAmount = amount;
|
||||
mLinearDarkAmount = linearAmount;
|
||||
mKeyguardStatusView.setDarkAmount(mInterpolatedDarkAmount);
|
||||
mKeyguardBottomArea.setDarkAmount(mInterpolatedDarkAmount);
|
||||
positionClockAndNotifications();
|
||||
mNotificationStackScroller.setDarkAmount(linearAmount, mInterpolatedDarkAmount);
|
||||
}
|
||||
|
||||
public void setPulsing(boolean pulsing) {
|
||||
@@ -2765,7 +2788,7 @@ public class NotificationPanelView extends PanelView implements
|
||||
public void dozeTimeTick() {
|
||||
mKeyguardStatusView.dozeTimeTick();
|
||||
mKeyguardBottomArea.dozeTimeTick();
|
||||
if (mDarkAmount > 0) {
|
||||
if (mInterpolatedDarkAmount > 0) {
|
||||
positionClockAndNotifications();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ import android.content.res.Resources;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
@@ -46,11 +45,9 @@ import android.support.annotation.NonNull;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
import android.support.v4.graphics.ColorUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.FloatProperty;
|
||||
import android.util.Log;
|
||||
import android.util.MathUtils;
|
||||
import android.util.Pair;
|
||||
import android.util.Property;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.InputDevice;
|
||||
import android.view.MotionEvent;
|
||||
@@ -375,25 +372,22 @@ public class NotificationStackScrollLayout extends ViewGroup
|
||||
private boolean mScrollable;
|
||||
private View mForcedScroll;
|
||||
private View mNeedingPulseAnimation;
|
||||
private float mDarkAmount = 0f;
|
||||
|
||||
/**
|
||||
* @see #setDarkAmount(float, float)
|
||||
*/
|
||||
private float mInterpolatedDarkAmount = 0f;
|
||||
|
||||
/**
|
||||
* @see #setDarkAmount(float, float)
|
||||
*/
|
||||
private float mLinearDarkAmount = 0f;
|
||||
|
||||
/**
|
||||
* How fast the background scales in the X direction as a factor of the Y expansion.
|
||||
*/
|
||||
private float mBackgroundXFactor = 1f;
|
||||
private static final Property<NotificationStackScrollLayout, Float> DARK_AMOUNT =
|
||||
new FloatProperty<NotificationStackScrollLayout>("darkAmount") {
|
||||
@Override
|
||||
public void setValue(NotificationStackScrollLayout object, float value) {
|
||||
object.setDarkAmount(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float get(NotificationStackScrollLayout object) {
|
||||
return object.getDarkAmount();
|
||||
}
|
||||
};
|
||||
private ObjectAnimator mDarkAmountAnimator;
|
||||
private boolean mUsingLightTheme;
|
||||
private boolean mQsExpanded;
|
||||
private boolean mForwardScrollable;
|
||||
@@ -424,6 +418,8 @@ public class NotificationStackScrollLayout extends ViewGroup
|
||||
private NotificationIconAreaController mIconAreaController;
|
||||
private float mVerticalPanelTranslation;
|
||||
|
||||
private Interpolator mDarkXInterpolator = Interpolators.FAST_OUT_SLOW_IN;
|
||||
|
||||
public NotificationStackScrollLayout(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
@@ -558,16 +554,16 @@ public class NotificationStackScrollLayout extends ViewGroup
|
||||
canvas.drawRect(darkLeft, darkTop, darkRight, darkBottom, mBackgroundPaint);
|
||||
}
|
||||
} else {
|
||||
float inverseDark = 1 - mDarkAmount;
|
||||
float yProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(inverseDark);
|
||||
float xProgress = Interpolators.FAST_OUT_SLOW_IN
|
||||
.getInterpolation(inverseDark * mBackgroundXFactor);
|
||||
float yProgress = 1 - mInterpolatedDarkAmount;
|
||||
float xProgress = mDarkXInterpolator.getInterpolation(
|
||||
(1 - mLinearDarkAmount) * mBackgroundXFactor);
|
||||
|
||||
mBackgroundAnimationRect.set(
|
||||
(int) MathUtils.lerp(darkLeft, lockScreenLeft, xProgress),
|
||||
(int) MathUtils.lerp(darkTop, lockScreenTop, yProgress),
|
||||
(int) MathUtils.lerp(darkRight, lockScreenRight, xProgress),
|
||||
(int) MathUtils.lerp(darkBottom, lockScreenBottom, yProgress));
|
||||
|
||||
if (!mAmbientState.isDark() || mFirstVisibleBackgroundChild != null) {
|
||||
canvas.drawRoundRect(mBackgroundAnimationRect.left, mBackgroundAnimationRect.top,
|
||||
mBackgroundAnimationRect.right, mBackgroundAnimationRect.bottom,
|
||||
@@ -585,14 +581,15 @@ public class NotificationStackScrollLayout extends ViewGroup
|
||||
|
||||
float alpha =
|
||||
BACKGROUND_ALPHA_DIMMED + (1 - BACKGROUND_ALPHA_DIMMED) * (1.0f - mDimAmount);
|
||||
alpha *= 1f - mDarkAmount;
|
||||
alpha *= 1f - mInterpolatedDarkAmount;
|
||||
// We need to manually blend in the background color.
|
||||
int scrimColor = mScrimController.getBackgroundColor();
|
||||
int awakeColor = ColorUtils.blendARGB(scrimColor, mBgColor, alpha);
|
||||
|
||||
// Interpolate between semi-transparent notification panel background color
|
||||
// and white AOD separator.
|
||||
float colorInterpolation = Interpolators.DECELERATE_QUINT.getInterpolation(mDarkAmount);
|
||||
float colorInterpolation = Interpolators.DECELERATE_QUINT.getInterpolation(
|
||||
mInterpolatedDarkAmount);
|
||||
int color = ColorUtils.blendARGB(awakeColor, Color.WHITE, colorInterpolation);
|
||||
|
||||
if (mCachedBackgroundColor != color) {
|
||||
@@ -736,7 +733,8 @@ public class NotificationStackScrollLayout extends ViewGroup
|
||||
}
|
||||
|
||||
private void updateAlgorithmHeightAndPadding() {
|
||||
mTopPadding = (int) MathUtils.lerp(mRegularTopPadding, mDarkTopPadding, mDarkAmount);
|
||||
mTopPadding = (int) MathUtils.lerp(mRegularTopPadding, mDarkTopPadding,
|
||||
mInterpolatedDarkAmount);
|
||||
mAmbientState.setLayoutHeight(getLayoutHeight());
|
||||
updateAlgorithmLayoutMinHeight();
|
||||
mAmbientState.setTopPadding(mTopPadding);
|
||||
@@ -961,7 +959,7 @@ public class NotificationStackScrollLayout extends ViewGroup
|
||||
}
|
||||
|
||||
public void updateClipping() {
|
||||
boolean animatingClipping = mDarkAmount > 0 && mDarkAmount < 1;
|
||||
boolean animatingClipping = mInterpolatedDarkAmount > 0 && mInterpolatedDarkAmount < 1;
|
||||
boolean clipped = mRequestedClipBounds != null && !mInHeadsUpPinnedMode
|
||||
&& !mHeadsUpAnimatingAway;
|
||||
if (mIsClipped != clipped) {
|
||||
@@ -2421,7 +2419,7 @@ public class NotificationStackScrollLayout extends ViewGroup
|
||||
return;
|
||||
}
|
||||
|
||||
final boolean awake = mDarkAmount != 0 || mAmbientState.isDark();
|
||||
final boolean awake = mInterpolatedDarkAmount != 0 || mAmbientState.isDark();
|
||||
mScrimController.setExcludedBackgroundArea(
|
||||
mFadingOut || mParentNotFullyVisible || awake || mIsClipped ? null
|
||||
: mCurrentBounds);
|
||||
@@ -3414,7 +3412,6 @@ public class NotificationStackScrollLayout extends ViewGroup
|
||||
.animateY(mShelf));
|
||||
ev.darkAnimationOriginIndex = mDarkAnimationOriginIndex;
|
||||
mAnimationEvents.add(ev);
|
||||
startDarkAmountAnimation();
|
||||
}
|
||||
mDarkNeedsAnimation = false;
|
||||
}
|
||||
@@ -3990,11 +3987,8 @@ public class NotificationStackScrollLayout extends ViewGroup
|
||||
if (animate && mAnimationsEnabled) {
|
||||
mDarkNeedsAnimation = true;
|
||||
mDarkAnimationOriginIndex = findDarkAnimationOriginIndex(touchWakeUpScreenLocation);
|
||||
mNeedsAnimation = true;
|
||||
mNeedsAnimation = true;
|
||||
} else {
|
||||
if (mDarkAmountAnimator != null) {
|
||||
mDarkAmountAnimator.cancel();
|
||||
}
|
||||
setDarkAmount(dark ? 1f : 0f);
|
||||
updateBackground();
|
||||
}
|
||||
@@ -4005,7 +3999,7 @@ public class NotificationStackScrollLayout extends ViewGroup
|
||||
}
|
||||
|
||||
private void updatePanelTranslation() {
|
||||
setTranslationX(mVerticalPanelTranslation + mAntiBurnInOffsetX * mDarkAmount);
|
||||
setTranslationX(mVerticalPanelTranslation + mAntiBurnInOffsetX * mInterpolatedDarkAmount);
|
||||
}
|
||||
|
||||
public void setVerticalPanelTranslation(float verticalPanelTranslation) {
|
||||
@@ -4024,9 +4018,22 @@ public class NotificationStackScrollLayout extends ViewGroup
|
||||
}
|
||||
|
||||
private void setDarkAmount(float darkAmount) {
|
||||
mDarkAmount = darkAmount;
|
||||
setDarkAmount(darkAmount, darkAmount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current dark amount.
|
||||
*
|
||||
* @param linearDarkAmount The dark amount that follows linear interpoloation in the animation,
|
||||
* i.e. animates from 0 to 1 or vice-versa in a linear manner.
|
||||
* @param interpolatedDarkAmount The dark amount that follows the actual interpolation of the
|
||||
* animation curve.
|
||||
*/
|
||||
public void setDarkAmount(float linearDarkAmount, float interpolatedDarkAmount) {
|
||||
mLinearDarkAmount = linearDarkAmount;
|
||||
mInterpolatedDarkAmount = interpolatedDarkAmount;
|
||||
boolean wasFullyDark = mAmbientState.isFullyDark();
|
||||
mAmbientState.setDarkAmount(darkAmount);
|
||||
mAmbientState.setDarkAmount(interpolatedDarkAmount);
|
||||
boolean nowFullyDark = mAmbientState.isFullyDark();
|
||||
if (nowFullyDark != wasFullyDark) {
|
||||
updateContentHeight();
|
||||
@@ -4044,42 +4051,24 @@ public class NotificationStackScrollLayout extends ViewGroup
|
||||
requestChildrenUpdate();
|
||||
}
|
||||
|
||||
public float getDarkAmount() {
|
||||
return mDarkAmount;
|
||||
public void notifyDarkAnimationStart(boolean dark) {
|
||||
// We only swap the scaling factor if we're fully dark or fully awake to avoid
|
||||
// interpolation issues when playing with the power button.
|
||||
if (mInterpolatedDarkAmount == 0 || mInterpolatedDarkAmount == 1) {
|
||||
mBackgroundXFactor = dark ? 1.8f : 1.5f;
|
||||
mDarkXInterpolator = dark
|
||||
? Interpolators.FAST_OUT_SLOW_IN_REVERSE
|
||||
: Interpolators.FAST_OUT_SLOW_IN;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel any previous dark animations - to avoid race conditions - and creates a new one.
|
||||
* This function also sets {@code mBackgroundXFactor} based on the current {@code mDarkAmount}.
|
||||
*/
|
||||
private void startDarkAmountAnimation() {
|
||||
boolean dark = mAmbientState.isDark();
|
||||
if (mDarkAmountAnimator != null) {
|
||||
mDarkAmountAnimator.cancel();
|
||||
}
|
||||
|
||||
public long getDarkAnimationDuration(boolean dark) {
|
||||
long duration = StackStateAnimator.ANIMATION_DURATION_WAKEUP;
|
||||
// Longer animation when sleeping with more than 1 notification
|
||||
if (dark && getNotGoneChildCount() > 2) {
|
||||
duration *= 1.2f;
|
||||
}
|
||||
|
||||
mDarkAmountAnimator = ObjectAnimator.ofFloat(this, DARK_AMOUNT, mDarkAmount,
|
||||
dark ? 1f : 0);
|
||||
// We only swap the scaling factor if we're fully dark or fully awake to avoid
|
||||
// interpolation issues when playing with the power button.
|
||||
if (mDarkAmount == 0 || mDarkAmount == 1) {
|
||||
mBackgroundXFactor = dark ? 2.5f : 1.5f;
|
||||
}
|
||||
mDarkAmountAnimator.setDuration(duration);
|
||||
mDarkAmountAnimator.setInterpolator(Interpolators.LINEAR);
|
||||
mDarkAmountAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
mDarkAmountAnimator = null;
|
||||
}
|
||||
});
|
||||
mDarkAmountAnimator.start();
|
||||
return duration;
|
||||
}
|
||||
|
||||
private int findDarkAnimationOriginIndex(@Nullable PointF screenLocation) {
|
||||
|
||||
Reference in New Issue
Block a user