diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java index 69f7828ff8fc0..009114ffa0bee 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java @@ -18,6 +18,8 @@ package com.android.systemui.bubbles; import static android.graphics.Paint.ANTI_ALIAS_FLAG; import static android.graphics.Paint.FILTER_BITMAP_FLAG; +import static com.android.systemui.Interpolators.ALPHA_IN; +import static com.android.systemui.Interpolators.ALPHA_OUT; import android.animation.ArgbEvaluator; import android.content.Context; @@ -56,6 +58,11 @@ public class BubbleFlyoutView extends FrameLayout { /** Max width of the flyout, in terms of percent of the screen width. */ private static final float FLYOUT_MAX_WIDTH_PERCENT = .6f; + /** Translation Y of fade animation. */ + private static final float FLYOUT_FADE_Y = 40f; + + private static final long FLYOUT_FADE_DURATION = 200L; + private final int mFlyoutPadding; private final int mFlyoutSpaceFromBubble; private final int mPointerSize; @@ -104,6 +111,9 @@ public class BubbleFlyoutView extends FrameLayout { /** The bounds of the flyout background, kept up to date as it transitions to the 'new' dot. */ private final RectF mBgRect = new RectF(); + /** The y position of the flyout, relative to the top of the screen. */ + private float mFlyoutY = 0f; + /** * Percent progress in the transition from flyout to 'new' dot. These two values are the inverse * of each other (if we're 40% transitioned to the dot, we're 60% flyout), but it makes the code @@ -221,18 +231,33 @@ public class BubbleFlyoutView extends FrameLayout { mSenderText.setTextSize(TypedValue.COMPLEX_UNIT_PX, newFontSize); } - /** Configures the flyout, collapsed into to dot form. */ - void setupFlyoutStartingAsDot( - Bubble.FlyoutMessage flyoutMessage, - PointF stackPos, - float parentWidth, - boolean arrowPointingLeft, - int dotColor, - @Nullable Runnable onLayoutComplete, - @Nullable Runnable onHide, - float[] dotCenter, - boolean hideDot) { + /* + * Fade animation for consecutive flyouts. + */ + void animateUpdate(Bubble.FlyoutMessage flyoutMessage, float parentWidth, float stackY) { + fade(false /* in */); + updateFlyoutMessage(flyoutMessage, parentWidth); + // Wait for TextViews to layout with updated height. + post(() -> { + mFlyoutY = stackY + (mBubbleSize - mFlyoutTextContainer.getHeight()) / 2f; + fade(true /* in */); + }); + } + private void fade(boolean in) { + setAlpha(in ? 0f : 1f); + setTranslationY(in ? mFlyoutY : mFlyoutY + FLYOUT_FADE_Y); + animate() + .alpha(in ? 1f : 0f) + .setDuration(FLYOUT_FADE_DURATION) + .setInterpolator(in ? ALPHA_IN : ALPHA_OUT); + animate() + .translationY(in ? mFlyoutY : mFlyoutY - FLYOUT_FADE_Y) + .setDuration(FLYOUT_FADE_DURATION) + .setInterpolator(in ? ALPHA_IN : ALPHA_OUT); + } + + private void updateFlyoutMessage(Bubble.FlyoutMessage flyoutMessage, float parentWidth) { final Drawable senderAvatar = flyoutMessage.senderAvatar; if (senderAvatar != null && flyoutMessage.isGroupChat) { mSenderAvatar.setVisibility(VISIBLE); @@ -256,6 +281,27 @@ public class BubbleFlyoutView extends FrameLayout { mSenderText.setVisibility(GONE); } + // Set the flyout TextView's max width in terms of percent, and then subtract out the + // padding so that the entire flyout view will be the desired width (rather than the + // TextView being the desired width + extra padding). + mMessageText.setMaxWidth(maxTextViewWidth); + mMessageText.setText(flyoutMessage.message); + } + + /** Configures the flyout, collapsed into dot form. */ + void setupFlyoutStartingAsDot( + Bubble.FlyoutMessage flyoutMessage, + PointF stackPos, + float parentWidth, + boolean arrowPointingLeft, + int dotColor, + @Nullable Runnable onLayoutComplete, + @Nullable Runnable onHide, + float[] dotCenter, + boolean hideDot) { + + updateFlyoutMessage(flyoutMessage, parentWidth); + mArrowPointingLeft = arrowPointingLeft; mDotColor = dotColor; mOnHide = onHide; @@ -263,24 +309,12 @@ public class BubbleFlyoutView extends FrameLayout { setCollapsePercent(1f); - // Set the flyout TextView's max width in terms of percent, and then subtract out the - // padding so that the entire flyout view will be the desired width (rather than the - // TextView being the desired width + extra padding). - mMessageText.setMaxWidth(maxTextViewWidth); - mMessageText.setText(flyoutMessage.message); - - // Wait for the TextView to lay out so we know its line count. + // Wait for TextViews to layout with updated height. post(() -> { - float restingTranslationY; - // Multi line flyouts get top-aligned to the bubble. - if (mMessageText.getLineCount() > 1) { - restingTranslationY = stackPos.y + mBubbleIconTopPadding; - } else { - // Single line flyouts are vertically centered with respect to the bubble. - restingTranslationY = - stackPos.y + (mBubbleSize - mFlyoutTextContainer.getHeight()) / 2f; - } - setTranslationY(restingTranslationY); + // Flyout is vertically centered with respect to the bubble. + mFlyoutY = + stackPos.y + (mBubbleSize - mFlyoutTextContainer.getHeight()) / 2f; + setTranslationY(mFlyoutY); // Calculate the translation required to position the flyout next to the bubble stack, // with the desired padding. @@ -300,7 +334,7 @@ public class BubbleFlyoutView extends FrameLayout { final float dotPositionY = stackPos.y + mDotCenter[1] - adjustmentForScaleAway; final float distanceFromFlyoutLeftToDotCenterX = mRestingTranslationX - dotPositionX; - final float distanceFromLayoutTopToDotCenterY = restingTranslationY - dotPositionY; + final float distanceFromLayoutTopToDotCenterY = mFlyoutY - dotPositionY; mTranslationXWhenDot = -distanceFromFlyoutLeftToDotCenterX; mTranslationYWhenDot = -distanceFromLayoutTopToDotCenterY; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index e2674de3a723a..431719f98ad93 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -2171,11 +2171,7 @@ public class BubbleStackView extends FrameLayout return getStatusBarHeight() + mBubbleSize + mBubblePaddingTop; } - /** - * Animates in the flyout for the given bubble, if available, and then hides it after some time. - */ - @VisibleForTesting - void animateInFlyoutForBubble(Bubble bubble) { + private boolean shouldShowFlyout(Bubble bubble) { Bubble.FlyoutMessage flyoutMessage = bubble.getFlyoutMessage(); final BadgedImageView bubbleView = bubble.getIconView(); if (flyoutMessage == null @@ -2187,11 +2183,22 @@ public class BubbleStackView extends FrameLayout || mIsGestureInProgress || mBubbleToExpandAfterFlyoutCollapse != null || bubbleView == null) { - if (bubbleView != null) { + if (bubbleView != null && mFlyout.getVisibility() != VISIBLE) { bubbleView.removeDotSuppressionFlag(BadgedImageView.SuppressionFlag.FLYOUT_VISIBLE); } // Skip the message if none exists, we're expanded or animating expansion, or we're // about to expand a bubble from the previous tapped flyout, or if bubble view is null. + return false; + } + return true; + } + + /** + * Animates in the flyout for the given bubble, if available, and then hides it after some time. + */ + @VisibleForTesting + void animateInFlyoutForBubble(Bubble bubble) { + if (!shouldShowFlyout(bubble)) { return; } @@ -2209,25 +2216,22 @@ public class BubbleStackView extends FrameLayout } // Stop suppressing the dot now that the flyout has morphed into the dot. - bubbleView.removeDotSuppressionFlag( + bubble.getIconView().removeDotSuppressionFlag( BadgedImageView.SuppressionFlag.FLYOUT_VISIBLE); - mFlyout.setVisibility(INVISIBLE); - // Hide the stack after a delay, if needed. updateTemporarilyInvisibleAnimation(false /* hideImmediately */); }; - mFlyout.setVisibility(INVISIBLE); // Suppress the dot when we are animating the flyout. - bubbleView.addDotSuppressionFlag( + bubble.getIconView().addDotSuppressionFlag( BadgedImageView.SuppressionFlag.FLYOUT_VISIBLE); // Start flyout expansion. Post in case layout isn't complete and getWidth returns 0. post(() -> { // An auto-expanding bubble could have been posted during the time it takes to // layout. - if (isExpanded()) { + if (isExpanded() || bubble.getIconView() == null) { return; } final Runnable expandFlyoutAfterDelay = () -> { @@ -2244,18 +2248,21 @@ public class BubbleStackView extends FrameLayout mFlyout.postDelayed(mAnimateInFlyout, 200); }; - if (bubble.getIconView() == null) { - return; - } - mFlyout.setupFlyoutStartingAsDot(flyoutMessage, - mStackAnimationController.getStackPosition(), getWidth(), - mStackAnimationController.isStackOnLeftSide(), - bubble.getIconView().getDotColor() /* dotColor */, - expandFlyoutAfterDelay /* onLayoutComplete */, - mAfterFlyoutHidden, - bubble.getIconView().getDotCenter(), - !bubble.showDot()); + if (mFlyout.getVisibility() == View.VISIBLE) { + mFlyout.animateUpdate(bubble.getFlyoutMessage(), getWidth(), + mStackAnimationController.getStackPosition().y); + } else { + mFlyout.setVisibility(INVISIBLE); + mFlyout.setupFlyoutStartingAsDot(bubble.getFlyoutMessage(), + mStackAnimationController.getStackPosition(), getWidth(), + mStackAnimationController.isStackOnLeftSide(), + bubble.getIconView().getDotColor() /* dotColor */, + expandFlyoutAfterDelay /* onLayoutComplete */, + mAfterFlyoutHidden, + bubble.getIconView().getDotCenter(), + !bubble.showDot()); + } mFlyout.bringToFront(); }); mFlyout.removeCallbacks(mHideFlyout);