Merge "Consecutive flyout animation"
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user