Merge "Consecutive flyout animation"

This commit is contained in:
Lyn Han
2020-10-14 18:02:23 +00:00
committed by Android (Google) Code Review
2 changed files with 93 additions and 52 deletions

View File

@@ -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;

View File

@@ -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);