Added new appear and disappear animations for heads up

The heads up notifications now appear directly out of the
statusbar icon in a smoother way. This also fixes the
landscape presentation, which was completely wrong
before.

Test: runtest systemui
Change-Id: I84e65d5216f74a9eb1717d3e7c111d66c0b43c65
Fixes: 72748440
This commit is contained in:
Selim Cinek
2018-03-16 17:37:50 -07:00
parent d03518cad5
commit 332c23fe4b
16 changed files with 313 additions and 81 deletions

View File

@@ -20,6 +20,7 @@
android:layout_width="match_parent"
android:visibility="invisible"
android:id="@+id/heads_up_status_bar_view"
android:alpha="0"
>
<!-- This is a space just used as a layout and it's not actually displaying anything. We're
repositioning the statusbar icon to the position where this is laid out when showing this

View File

@@ -26,6 +26,7 @@ import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.MathUtils;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewAnimationUtils;
@@ -178,6 +179,10 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
private boolean mNeedsDimming;
private int mDimmedAlpha;
private boolean mBlockNextTouch;
private boolean mIsHeadsUpAnimation;
private int mHeadsUpAddStartLocation;
private float mHeadsUpLocation;
private boolean mIsAppearing;
public ActivatableNotificationView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -204,6 +209,18 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
makeInactive(true /* animate */);
}
}, super::performClick, this::handleSlideBack, mFalsingManager::onNotificationDoubleTap);
initDimens();
}
private void initDimens() {
mHeadsUpAddStartLocation = getResources().getDimensionPixelSize(
com.android.internal.R.dimen.notification_content_margin_start);
}
@Override
public void onDensityOrFontScaleChanged() {
super.onDensityOrFontScaleChanged();
initDimens();
}
@Override
@@ -745,27 +762,34 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
}
@Override
public void performRemoveAnimation(long duration, float translationDirection,
Runnable onFinishedRunnable) {
public void performRemoveAnimation(long duration, long delay,
float translationDirection, boolean isHeadsUpAnimation, float endLocation,
Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) {
enableAppearDrawing(true);
mIsHeadsUpAnimation = isHeadsUpAnimation;
mHeadsUpLocation = endLocation;
if (mDrawingAppearAnimation) {
startAppearAnimation(false /* isAppearing */, translationDirection,
0, duration, onFinishedRunnable);
delay, duration, onFinishedRunnable, animationListener);
} else if (onFinishedRunnable != null) {
onFinishedRunnable.run();
}
}
@Override
public void performAddAnimation(long delay, long duration) {
public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear) {
enableAppearDrawing(true);
mIsHeadsUpAnimation = isHeadsUpAppear;
mHeadsUpLocation = mHeadsUpAddStartLocation;
if (mDrawingAppearAnimation) {
startAppearAnimation(true /* isAppearing */, -1.0f, delay, duration, null);
startAppearAnimation(true /* isAppearing */, isHeadsUpAppear ? 0.0f : -1.0f, delay,
duration, null, null);
}
}
private void startAppearAnimation(boolean isAppearing, float translationDirection, long delay,
long duration, final Runnable onFinishedRunnable) {
long duration, final Runnable onFinishedRunnable,
AnimatorListenerAdapter animationListener) {
cancelAppearAnimation();
mAnimationTranslationY = translationDirection * getActualHeight();
if (mAppearAnimationFraction == -1.0f) {
@@ -778,6 +802,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
mAppearAnimationTranslation = 0;
}
}
mIsAppearing = isAppearing;
float targetValue;
if (isAppearing) {
@@ -803,6 +828,9 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
invalidate();
}
});
if (animationListener != null) {
mAppearAnimator.addListener(animationListener);
}
if (delay > 0) {
// we need to apply the initial state already to avoid drawn frames in the wrong state
updateAppearAnimationAlpha();
@@ -862,9 +890,21 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
/ (HORIZONTAL_ANIMATION_START - HORIZONTAL_ANIMATION_END);
widthFraction = Math.min(1.0f, Math.max(0.0f, widthFraction));
widthFraction = mCurrentAppearInterpolator.getInterpolation(widthFraction);
float left = (getWidth() * (0.5f - HORIZONTAL_COLLAPSED_REST_PARTIAL / 2.0f) *
widthFraction);
float right = getWidth() - left;
float startWidthFraction = HORIZONTAL_COLLAPSED_REST_PARTIAL;
if (mIsHeadsUpAnimation && !mIsAppearing) {
startWidthFraction = 0;
}
float width = MathUtils.lerp(startWidthFraction, 1.0f, 1.0f - widthFraction)
* getWidth();
float left;
float right;
if (mIsHeadsUpAnimation) {
left = MathUtils.lerp(mHeadsUpLocation, 0, 1.0f - widthFraction);
right = left + width;
} else {
left = getWidth() * 0.5f - width / 2.0f;
right = getWidth() - left;
}
// handle top animation
float heightFraction = (inverseFraction - (1.0f - VERTICAL_ANIMATION_START)) /

View File

@@ -16,6 +16,7 @@
package com.android.systemui.statusbar;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.graphics.Paint;
import android.graphics.Rect;
@@ -296,19 +297,24 @@ public abstract class ExpandableView extends FrameLayout {
/**
* Perform a remove animation on this view.
*
* @param duration The duration of the remove animation.
* @param delay The delay of the animation
* @param translationDirection The direction value from [-1 ... 1] indicating in which the
* animation should be performed. A value of -1 means that The
* remove animation should be performed upwards,
* such that the child appears to be going away to the top. 1
* Should mean the opposite.
* animation should be performed. A value of -1 means that The
* remove animation should be performed upwards,
* such that the child appears to be going away to the top. 1
* Should mean the opposite.
* @param isHeadsUpAnimation Is this a headsUp animation.
* @param endLocation The location where the horizonal heads up disappear animation should end.
* @param onFinishedRunnable A runnable which should be run when the animation is finished.
* @param animationListener An animation listener to add to the animation.
*/
public abstract void performRemoveAnimation(long duration, float translationDirection,
Runnable onFinishedRunnable);
public abstract void performRemoveAnimation(long duration,
long delay, float translationDirection, boolean isHeadsUpAnimation, float endLocation,
Runnable onFinishedRunnable,
AnimatorListenerAdapter animationListener);
public abstract void performAddAnimation(long delay, long duration);
public abstract void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear);
/**
* Set the notification appearance to be below the speed bump.

View File

@@ -17,6 +17,7 @@
package com.android.systemui.statusbar;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
import android.util.AttributeSet;
@@ -37,10 +38,15 @@ public class HeadsUpStatusBarView extends AlphaOptimizedLinearLayout {
private View mIconPlaceholder;
private TextView mTextView;
private NotificationData.Entry mShowingEntry;
private Rect mIconRect = new Rect();
private Rect mLayoutedIconRect = new Rect();
private int[] mTmpPosition = new int[2];
private boolean mFirstLayout = true;
private boolean mPublicMode;
private int mMaxWidth;
private View mRootView;
private int mLeftInset;
private Rect mIconDrawingRect = new Rect();
private Runnable mOnDrawingRectChangedListener;
public HeadsUpStatusBarView(Context context) {
this(context, null);
@@ -64,6 +70,33 @@ public class HeadsUpStatusBarView extends AlphaOptimizedLinearLayout {
mEndMargin = res.getDimensionPixelSize(
com.android.internal.R.dimen.notification_content_margin_end);
setPaddingRelative(mAbsoluteStartPadding, 0, mEndMargin, 0);
updateMaxWidth();
}
private void updateMaxWidth() {
int maxWidth = getResources().getDimensionPixelSize(R.dimen.qs_panel_width);
if (maxWidth != mMaxWidth) {
// maxWidth doesn't work with fill_parent, let's manually make it at most as big as the
// notification panel
mMaxWidth = maxWidth;
requestLayout();
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mMaxWidth > 0) {
int newSize = Math.min(MeasureSpec.getSize(widthMeasureSpec), mMaxWidth);
widthMeasureSpec = MeasureSpec.makeMeasureSpec(newSize,
MeasureSpec.getMode(widthMeasureSpec));
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
updateMaxWidth();
}
@VisibleForTesting
@@ -97,13 +130,15 @@ public class HeadsUpStatusBarView extends AlphaOptimizedLinearLayout {
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
mIconPlaceholder.getLocationOnScreen(mTmpPosition);
int left = mTmpPosition[0];
int left = (int) (mTmpPosition[0] - getTranslationX());
int top = mTmpPosition[1];
int right = left + mIconPlaceholder.getWidth();
int bottom = top + mIconPlaceholder.getHeight();
mIconRect.set(left, top, right, bottom);
if (left != mAbsoluteStartPadding) {
int newPadding = mAbsoluteStartPadding - left + getPaddingStart();
mLayoutedIconRect.set(left, top, right, bottom);
updateDrawingRect();
int targetPadding = mAbsoluteStartPadding + mLeftInset;
if (left != targetPadding) {
int newPadding = targetPadding - left + getPaddingStart();
setPaddingRelative(newPadding, 0, mEndMargin, 0);
}
if (mFirstLayout) {
@@ -115,12 +150,33 @@ public class HeadsUpStatusBarView extends AlphaOptimizedLinearLayout {
}
}
@Override
public void setTranslationX(float translationX) {
super.setTranslationX(translationX);
updateDrawingRect();
}
private void updateDrawingRect() {
float oldLeft = mIconDrawingRect.left;
mIconDrawingRect.set(mLayoutedIconRect);
mIconDrawingRect.offset((int) getTranslationX(), 0);
if (oldLeft != mIconDrawingRect.left && mOnDrawingRectChangedListener != null) {
mOnDrawingRectChangedListener.run();
}
}
@Override
protected boolean fitSystemWindows(Rect insets) {
mLeftInset = insets.left;
return super.fitSystemWindows(insets);
}
public NotificationData.Entry getShowingEntry() {
return mShowingEntry;
}
public Rect getIconDrawingRect() {
return mIconRect;
return mIconDrawingRect;
}
public void onDarkChanged(Rect area, float darkIntensity, int tint) {
@@ -130,4 +186,8 @@ public class HeadsUpStatusBarView extends AlphaOptimizedLinearLayout {
public void setPublicMode(boolean publicMode) {
mPublicMode = publicMode;
}
public void setOnDrawingRectChangedListener(Runnable onDrawingRectChangedListener) {
mOnDrawingRectChangedListener = onDrawingRectChangedListener;
}
}

View File

@@ -16,6 +16,7 @@
package com.android.systemui.statusbar;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
@@ -112,14 +113,16 @@ public abstract class StackScrollerDecorView extends ExpandableView {
}
@Override
public void performRemoveAnimation(long duration, float translationDirection,
Runnable onFinishedRunnable) {
public void performRemoveAnimation(long duration, long delay,
float translationDirection, boolean isHeadsUpAnimation, float endLocation,
Runnable onFinishedRunnable,
AnimatorListenerAdapter animationListener) {
// TODO: Use duration
performVisibilityAnimation(false);
}
@Override
public void performAddAnimation(long delay, long duration) {
public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear) {
// TODO: use delay and duration
performVisibilityAnimation(true);
}

View File

@@ -72,20 +72,37 @@ class HeadsUpAppearanceController implements OnHeadsUpChangedListener,
mHeadsUpManager = headsUpManager;
mHeadsUpManager.addListener(this);
mHeadsUpStatusBarView = headsUpStatusBarView;
headsUpStatusBarView.setOnDrawingRectChangedListener(
() -> updateIsolatedIconLocation(true /* requireUpdate */));
mStackScroller = stackScroller;
panelView.addTrackingHeadsUpListener(this::setTrackingHeadsUp);
panelView.setVerticalTranslationListener(this::updatePanelTranslation);
panelView.setHeadsUpAppearanceController(this);
mStackScroller.addOnExpandedHeightListener(this::setExpandedHeight);
mStackScroller.addOnLayoutChangeListener(
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom)
-> updatePanelTranslation());
mClockView = clockView;
mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class);
mDarkIconDispatcher.addDarkReceiver(this);
}
private void updateIsolatedIconLocation(boolean requireStateUpdate) {
mNotificationIconAreaController.setIsolatedIconLocation(
mHeadsUpStatusBarView.getIconDrawingRect(), requireStateUpdate);
}
@Override
public void onHeadsUpPinned(ExpandableNotificationRow headsUp) {
updateTopEntry();
updateHeader(headsUp.getEntry());
}
public void updatePanelTranslation() {
float newTranslation = mStackScroller.getLeft() + mStackScroller.getTranslationX();
mHeadsUpStatusBarView.setTranslationX(newTranslation);
}
private void updateTopEntry() {
NotificationData.Entry newEntry = null;
if (!mIsExpanded && mHeadsUpManager.hasPinnedHeadsUp()) {
@@ -104,9 +121,11 @@ class HeadsUpAppearanceController implements OnHeadsUpChangedListener,
// We now have a headsUp and didn't have one before. Let's start the disappear
// animation
setShown(true);
animateIsolation = !mIsExpanded;
}
updateIsolatedIconLocation(false /* requireUpdate */);
mNotificationIconAreaController.showIconIsolated(newEntry == null ? null
: newEntry.icon, mHeadsUpStatusBarView.getIconDrawingRect(), animateIsolation);
: newEntry.icon, animateIsolation);
}
}
@@ -134,6 +153,16 @@ class HeadsUpAppearanceController implements OnHeadsUpChangedListener,
return mShown;
}
/**
* Should the headsup status bar view be visible right now? This may be different from isShown,
* since the headsUp manager might not have notified us yet of the state change.
*
* @return if the heads up status bar view should be shown
*/
public boolean shouldBeVisible() {
return !mIsExpanded && mHeadsUpManager.hasPinnedHeadsUp();
}
@Override
public void onHeadsUpUnPinned(ExpandableNotificationRow headsUp) {
updateTopEntry();

View File

@@ -311,8 +311,11 @@ public class NotificationIconAreaController implements DarkReceiver {
mShelfIcons.setDark(dark, false, 0);
}
public void showIconIsolated(StatusBarIconView icon, Rect absoluteIconPosition,
boolean animated) {
mNotificationIcons.showIconIsolated(icon, absoluteIconPosition, animated);
public void showIconIsolated(StatusBarIconView icon, boolean animated) {
mNotificationIcons.showIconIsolated(icon, animated);
}
public void setIsolatedIconLocation(Rect iconDrawingRect, boolean requireStateUpdate) {
mNotificationIcons.setIsolatedIconLocation(iconDrawingRect, requireStateUpdate);
}
}

View File

@@ -16,6 +16,9 @@
package com.android.systemui.statusbar.phone;
import static com.android.systemui.statusbar.phone.HeadsUpAppearanceController.CONTENT_FADE_DURATION;
import static com.android.systemui.statusbar.phone.HeadsUpAppearanceController.CONTENT_FADE_DELAY;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
@@ -109,8 +112,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
public AnimationFilter getAnimationFilter() {
return mAnimationFilter;
}
}.setDuration(HeadsUpAppearanceController.CONTENT_FADE_DURATION).setDelay(
HeadsUpAppearanceController.CONTENT_FADE_DURATION);
}.setDuration(CONTENT_FADE_DURATION);
/**
* The animation property used for the icon when its isolation ends.
@@ -123,7 +125,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
public AnimationFilter getAnimationFilter() {
return mAnimationFilter;
}
}.setDuration(HeadsUpAppearanceController.CONTENT_FADE_DURATION);
}.setDuration(CONTENT_FADE_DURATION);
public static final int MAX_VISIBLE_ICONS_WHEN_DARK = 5;
public static final int MAX_STATIC_ICONS = 4;
@@ -319,7 +321,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
boolean isIsolatedIcon = child == mIsolatedIcon;
icon.setVisibleState(StatusBarIconView.STATE_HIDDEN, true /* animate */,
() -> removeTransientView(icon),
isIsolatedIcon ? HeadsUpAppearanceController.CONTENT_FADE_DURATION : 0);
isIsolatedIcon ? CONTENT_FADE_DURATION : 0);
}
}
}
@@ -620,16 +622,21 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
mReplacingIcons = replacingIcons;
}
public void showIconIsolated(StatusBarIconView icon, Rect absoluteIconPosition,
boolean animated) {
public void showIconIsolated(StatusBarIconView icon, boolean animated) {
if (animated) {
mIsolatedIconForAnimation = mIsolatedIcon;
mIsolatedIconForAnimation = icon != null ? icon : mIsolatedIcon;
}
mIsolatedIcon = icon;
mIsolatedIconLocation = absoluteIconPosition;
updateState();
}
public void setIsolatedIconLocation(Rect isolatedIconLocation, boolean requireUpdate) {
mIsolatedIconLocation = isolatedIconLocation;
if (requireUpdate) {
updateState();
}
}
public class IconState extends ViewState {
public static final int NO_VALUE = NotificationIconContainer.NO_VALUE;
public float iconAppearAmount = 1.0f;
@@ -706,8 +713,12 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
if (mIsolatedIconForAnimation != null) {
if (view == mIsolatedIconForAnimation) {
animationProperties = UNISOLATION_PROPERTY;
animationProperties.setDelay(
mIsolatedIcon != null ? CONTENT_FADE_DELAY : 0);
} else {
animationProperties = UNISOLATION_PROPERTY_OTHERS;
animationProperties.setDelay(
mIsolatedIcon == null ? CONTENT_FADE_DELAY : 0);
}
animate = true;
}

View File

@@ -247,6 +247,8 @@ public class NotificationPanelView extends PanelView implements
private int mStackScrollerMeasuringPass;
private ArrayList<Consumer<ExpandableNotificationRow>> mTrackingHeadsUpListeners
= new ArrayList<>();
private Runnable mVerticalTranslationListener;
private HeadsUpAppearanceController mHeadsUpAppearanceController;
public NotificationPanelView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -2413,6 +2415,9 @@ public class NotificationPanelView extends PanelView implements
protected void setVerticalPanelTranslation(float translation) {
mNotificationStackScroller.setTranslationX(translation);
mQsFrame.setTranslationX(translation);
if (mVerticalTranslationListener != null) {
mVerticalTranslationListener.run();
}
}
protected void updateExpandedHeight(float expandedHeight) {
@@ -2568,6 +2573,10 @@ public class NotificationPanelView extends PanelView implements
if (mLaunchingNotification) {
return mHideIconsDuringNotificationLaunch;
}
if (mHeadsUpAppearanceController != null
&& mHeadsUpAppearanceController.shouldBeVisible()) {
return false;
}
return !isFullWidth() || !mShowIconsWhenExpanded;
}
@@ -2713,4 +2722,13 @@ public class NotificationPanelView extends PanelView implements
public void addTrackingHeadsUpListener(Consumer<ExpandableNotificationRow> listener) {
mTrackingHeadsUpListeners.add(listener);
}
public void setVerticalTranslationListener(Runnable verticalTranslationListener) {
mVerticalTranslationListener = verticalTranslationListener;
}
public void setHeadsUpAppearanceController(
HeadsUpAppearanceController headsUpAppearanceController) {
mHeadsUpAppearanceController = headsUpAppearanceController;
}
}

View File

@@ -21,12 +21,12 @@ import android.util.Property;
import android.view.View;
import java.util.ArrayList;
import java.util.Set;
/**
* Filters the animations for only a certain type of properties.
*/
public class AnimationFilter {
public static final int NO_DELAY = -1;
boolean animateAlpha;
boolean animateX;
boolean animateY;
@@ -40,7 +40,7 @@ public class AnimationFilter {
public boolean animateShadowAlpha;
boolean hasDelays;
boolean hasGoToFullShadeEvent;
boolean hasHeadsUpDisappearClickEvent;
long customDelay;
private ArraySet<Property> mAnimatedProperties = new ArraySet<>();
public AnimationFilter animateAlpha() {
@@ -129,8 +129,14 @@ public class AnimationFilter {
hasGoToFullShadeEvent = true;
}
if (ev.animationType == NotificationStackScrollLayout.AnimationEvent
.ANIMATION_TYPE_HEADS_UP_DISAPPEAR) {
customDelay = StackStateAnimator.ANIMATION_DELAY_HEADS_UP;
} else if (ev.animationType == NotificationStackScrollLayout.AnimationEvent
.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK) {
hasHeadsUpDisappearClickEvent = true;
// We need both timeouts when clicking, one to delay it and one for the animation
// to look nice
customDelay = StackStateAnimator.ANIMATION_DELAY_HEADS_UP_CLICKED
+ StackStateAnimator.ANIMATION_DELAY_HEADS_UP;
}
}
}
@@ -165,7 +171,7 @@ public class AnimationFilter {
animateHideSensitive = false;
hasDelays = false;
hasGoToFullShadeEvent = false;
hasHeadsUpDisappearClickEvent = false;
customDelay = NO_DELAY;
mAnimatedProperties.clear();
}

View File

@@ -233,7 +233,8 @@ public class ExpandableViewState extends ViewState {
expandableView.setDark(this.dark, animationFilter.animateDark, properties.delay);
if (properties.wasAdded(child) && !hidden) {
expandableView.performAddAnimation(properties.delay, properties.duration);
expandableView.performAddAnimation(properties.delay, properties.duration,
false /* isHeadsUpAppear */);
}
if (!expandableView.isInShelf() && this.inShelf) {

View File

@@ -23,6 +23,11 @@ import android.view.animation.PathInterpolator;
* An interpolator specifically designed for the appear animation of heads up notifications.
*/
public class HeadsUpAppearInterpolator extends PathInterpolator {
private static float X1 = 250f;
private static float X2 = 200f;
private static float XTOT = (X1 + X2);;
public HeadsUpAppearInterpolator() {
super(getAppearPath());
}
@@ -30,22 +35,18 @@ public class HeadsUpAppearInterpolator extends PathInterpolator {
private static Path getAppearPath() {
Path path = new Path();
path.moveTo(0, 0);
float x1 = 250f;
float x2 = 150f;
float x3 = 100f;
float y1 = 90f;
float y2 = 78f;
float y3 = 80f;
float xTot = (x1 + x2 + x3);
path.cubicTo(x1 * 0.9f / xTot, 0f,
x1 * 0.8f / xTot, y1 / y3,
x1 / xTot , y1 / y3);
path.cubicTo((x1 + x2 * 0.4f) / xTot, y1 / y3,
(x1 + x2 * 0.2f) / xTot, y2 / y3,
(x1 + x2) / xTot, y2 / y3);
path.cubicTo((x1 + x2 + x3 * 0.4f) / xTot, y2 / y3,
(x1 + x2 + x3 * 0.2f) / xTot, 1f,
1f, 1f);
float y2 = 80f;
path.cubicTo(X1 * 0.8f / XTOT, y1 / y2,
X1 * 0.8f / XTOT, y1 / y2,
X1 / XTOT, y1 / y2);
path.cubicTo((X1 + X2 * 0.4f) / XTOT, y1 / y2,
(X1 + X2 * 0.2f) / XTOT, 1.0f,
1.0f , 1.0f);
return path;
}
public static float getFractionUntilOvershoot() {
return X1 / XTOT;
}
}

View File

@@ -4885,7 +4885,8 @@ public class NotificationStackScrollLayout extends ViewGroup
.animateHeight()
.animateTopInset()
.animateY()
.animateZ(),
.animateZ()
.hasDelays(),
// ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
new AnimationFilter()

View File

@@ -528,9 +528,6 @@ public class StackScrollAlgorithm {
childViewState.inShelf = true;
childViewState.headsUpIsVisible = false;
}
if (!ambientState.isShadeExpanded()) {
childViewState.height = (int) (mStatusBarHeight - childViewState.yTranslation);
}
}
protected int getMaxAllowedChildHeight(View child) {

View File

@@ -29,6 +29,7 @@ import com.android.systemui.R;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarIconView;
import java.util.ArrayList;
import java.util.HashSet;
@@ -45,13 +46,17 @@ public class StackStateAnimator {
public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464;
public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220;
public static final int ANIMATION_DURATION_CLOSE_REMOTE_INPUT = 150;
public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 650;
public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 230;
public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 550;
public static final int ANIMATION_DURATION_HEADS_UP_APPEAR_CLOSED
= (int) (ANIMATION_DURATION_HEADS_UP_APPEAR
* HeadsUpAppearInterpolator.getFractionUntilOvershoot());
public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 300;
public static final int ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING = 80;
public static final int ANIMATION_DELAY_PER_ELEMENT_MANUAL = 32;
public static final int ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE = 48;
public static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE = 2;
public static final int ANIMATION_DELAY_HEADS_UP = 120;
public static final int ANIMATION_DELAY_HEADS_UP_CLICKED= 120;
private final int mGoToFullShadeAppearingTranslation;
private final ExpandableViewState mTmpState = new ExpandableViewState();
@@ -74,8 +79,9 @@ public class StackStateAnimator {
private ValueAnimator mBottomOverScrollAnimator;
private int mHeadsUpAppearHeightBottom;
private boolean mShadeExpanded;
private ArrayList<View> mChildrenToClearFromOverlay = new ArrayList<>();
private NotificationShelf mShelf;
private float mStatusBarIconLocation;
private int[] mTmpLocation = new int[2];
public StackStateAnimator(NotificationStackScrollLayout hostLayout) {
mHostLayout = hostLayout;
@@ -222,8 +228,8 @@ public class StackStateAnimator {
if (mAnimationFilter.hasGoToFullShadeEvent) {
return calculateDelayGoToFullShade(viewState);
}
if (mAnimationFilter.hasHeadsUpDisappearClickEvent) {
return ANIMATION_DELAY_HEADS_UP;
if (mAnimationFilter.customDelay != AnimationFilter.NO_DELAY) {
return mAnimationFilter.customDelay;
}
long minDelay = 0;
for (NotificationStackScrollLayout.AnimationEvent event : mNewEvents) {
@@ -327,10 +333,6 @@ public class StackStateAnimator {
private void onAnimationFinished() {
mHostLayout.onChildAnimationFinished();
for (View v : mChildrenToClearFromOverlay) {
removeFromOverlay(v);
}
mChildrenToClearFromOverlay.clear();
}
/**
@@ -396,13 +398,14 @@ public class StackStateAnimator {
}
changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR,
translationDirection, new Runnable() {
0 /* delay */, translationDirection, false /* isHeadsUpAppear */,
0, new Runnable() {
@Override
public void run() {
// remove the temporary overlay
removeFromOverlay(changingView);
}
});
}, null);
} else if (event.animationType ==
NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) {
// A race condition can trigger the view to be added to the overlay even though
@@ -424,7 +427,9 @@ public class StackStateAnimator {
if (event.headsUpFromBottom) {
mTmpState.yTranslation = mHeadsUpAppearHeightBottom;
} else {
mTmpState.yTranslation = -mTmpState.height;
mTmpState.yTranslation = 0;
changingView.performAddAnimation(0, ANIMATION_DURATION_HEADS_UP_APPEAR_CLOSED,
true /* isHeadsUpAppear */);
}
mHeadsUpAppearChildren.add(changingView);
mTmpState.applyToView(changingView);
@@ -433,22 +438,56 @@ public class StackStateAnimator {
event.animationType == NotificationStackScrollLayout
.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK) {
mHeadsUpDisappearChildren.add(changingView);
Runnable endRunnable = null;
// We need some additional delay in case we were removed to make sure we're not
// lagging
int extraDelay = event.animationType == NotificationStackScrollLayout
.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
? ANIMATION_DELAY_HEADS_UP_CLICKED
: 0;
if (changingView.getParent() == null) {
// This notification was actually removed, so we need to add it to the overlay
mHostLayout.getOverlay().add(changingView);
mTmpState.initFrom(changingView);
mTmpState.yTranslation = -changingView.getActualHeight();
mTmpState.yTranslation = 0;
// We temporarily enable Y animations, the real filter will be combined
// afterwards anyway
mAnimationFilter.animateY = true;
mAnimationProperties.delay =
event.animationType == NotificationStackScrollLayout
.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
? ANIMATION_DELAY_HEADS_UP
: 0;
mAnimationProperties.delay = extraDelay + ANIMATION_DELAY_HEADS_UP;
mAnimationProperties.duration = ANIMATION_DURATION_HEADS_UP_DISAPPEAR;
mTmpState.animateTo(changingView, mAnimationProperties);
mChildrenToClearFromOverlay.add(changingView);
endRunnable = () -> {
// remove the temporary overlay
removeFromOverlay(changingView);
};
}
float targetLocation = 0;
boolean needsAnimation = true;
if (changingView instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) changingView;
if (row.isDismissed()) {
needsAnimation = false;
}
StatusBarIconView icon = row.getEntry().icon;
if (icon.getParent() != null) {
icon.getLocationOnScreen(mTmpLocation);
float iconPosition = mTmpLocation[0] - icon.getTranslationX()
+ ViewState.getFinalTranslationX(icon) + icon.getWidth() * 0.25f;
mHostLayout.getLocationOnScreen(mTmpLocation);
targetLocation = iconPosition - mTmpLocation[0];
}
}
if (needsAnimation) {
// We need to add the global animation listener, since once no animations are
// running anymore, the panel will instantly hide itself. We need to wait until
// the animation is fully finished for this though.
changingView.performRemoveAnimation(ANIMATION_DURATION_HEADS_UP_DISAPPEAR
+ ANIMATION_DELAY_HEADS_UP, extraDelay, 0.0f,
true /* isHeadsUpAppear */, targetLocation, endRunnable,
getGlobalAnimationFinishedListener());
} else if (endRunnable != null) {
endRunnable.run();
}
}
mNewEvents.add(event);

View File

@@ -640,6 +640,22 @@ public class ViewState {
return newDuration;
}
/**
* Get the end value of the xTranslation animation running on a view or the xTranslation
* if no animation is running.
*/
public static float getFinalTranslationX(View view) {
if (view == null) {
return 0;
}
ValueAnimator xAnimator = getChildTag(view, TAG_ANIMATOR_TRANSLATION_X);
if (xAnimator == null) {
return view.getTranslationX();
} else {
return getChildTag(view, TAG_END_TRANSLATION_X);
}
}
/**
* Get the end value of the yTranslation animation running on a view or the yTranslation
* if no animation is running.