Animate the bubble bar to show new bubble
This change updates that animation that plays when a new bubble notification is received. We now animate the entire bubble bar rather than the individual bubble.
Demo: http://recall/-/bJtug1HhvXkkeA4MQvIaiP/dwbsZJZlqLwJ2IG7RfJZ7c
Flag: ACONFIG com.android.wm.shell.enable_bubble_bar DEVELOPMENT
Bug: 280605846
Test: atest BubbleBarViewAnimatorTest
Change-Id: I2dc58ad61b880d76c9eefa462c3aee4e8bbb3584
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
index 95c4e25..4a8ed87 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
@@ -155,13 +155,16 @@
)
// if there's an animating bubble add it to the touch region so that it's clickable
- val animatingBubbleBounds =
+ val isAnimatingNewBubble =
controllers.bubbleControllers
.getOrNull()
?.bubbleBarViewController
- ?.animatingBubbleBounds
- if (animatingBubbleBounds != null) {
- defaultTouchableRegion.op(animatingBubbleBounds, Region.Op.UNION)
+ ?.isAnimatingNewBubble
+ ?: false
+ if (isAnimatingNewBubble) {
+ val iconBounds =
+ controllers.bubbleControllers.get().bubbleBarViewController.bubbleBarBounds
+ defaultTouchableRegion.op(iconBounds, Region.Op.UNION)
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index 5234936..8c6cbc9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -104,8 +104,6 @@
* updates the bounds and accounts for translation.
*/
private final Rect mBubbleBarBounds = new Rect();
- /** The bounds of the animating bubble in the coordinate space of the BubbleBarView. */
- private final Rect mAnimatingBubbleBounds = new Rect();
// The amount the bubbles overlap when they are stacked in the bubble bar
private final float mIconOverlapAmount;
// The spacing between the bubbles when bubble bar is expanded
@@ -185,7 +183,7 @@
setClipToPadding(false);
- mBubbleBarBackground = new BubbleBarBackground(context, getBubbleBarHeight());
+ mBubbleBarBackground = new BubbleBarBackground(context, getBubbleBarExpandedHeight());
setBackgroundDrawable(mBubbleBarBackground);
mWidthAnimator.setDuration(WIDTH_ANIMATION_DURATION_MS);
@@ -246,7 +244,7 @@
params.width = (int) mIconSize;
childView.setLayoutParams(params);
}
- mBubbleBarBackground.setHeight(getBubbleBarHeight());
+ mBubbleBarBackground.setHeight(getBubbleBarExpandedHeight());
updateLayoutParams();
}
@@ -462,30 +460,6 @@
return mBubbleBarBounds;
}
- /** Returns the bounds of the animating bubble, or {@code null} if no bubble is animating. */
- @Nullable
- public Rect getAnimatingBubbleBounds() {
- if (mIsAnimatingNewBubble) {
- return mAnimatingBubbleBounds;
- }
- return null;
- }
-
- /**
- * Updates the animating bubble bounds. This should be called when the bubble is fully animated
- * in so that we can include it in taskbar touchable region.
- *
- * <p>The bounds are adjusted to the coordinate space of BubbleBarView so that it can be used
- * by taskbar.
- */
- public void updateAnimatingBubbleBounds(int left, int top, int width, int height) {
- Rect bubbleBarBounds = getBubbleBarBounds();
- mAnimatingBubbleBounds.left = bubbleBarBounds.left + left;
- mAnimatingBubbleBounds.top = bubbleBarBounds.top + top;
- mAnimatingBubbleBounds.right = mAnimatingBubbleBounds.left + width;
- mAnimatingBubbleBounds.bottom = mAnimatingBubbleBounds.top + height;
- }
-
/**
* Set bubble bar relative pivot value for X and Y, applied as a fraction of view width/height
* respectively. If the value is not in range of 0 to 1 it will be normalized.
@@ -498,6 +472,11 @@
requestLayout();
}
+ /** Like {@link #setRelativePivot(float, float)} but only updates pivot y. */
+ public void setRelativePivotY(float y) {
+ setRelativePivot(mRelativePivotX, y);
+ }
+
/**
* Get current relative pivot for X axis
*/
@@ -512,38 +491,14 @@
return mRelativePivotY;
}
- /** Prepares for animating a bubble while being stashed. */
- public void prepareForAnimatingBubbleWhileStashed(String bubbleKey) {
+ /** Notifies the bubble bar that a new bubble animation is starting. */
+ public void onAnimatingBubbleStarted() {
mIsAnimatingNewBubble = true;
- // we're about to animate the new bubble in. the new bubble has already been added to this
- // view, but we're currently stashed, so before we can start the animation we need make
- // everything else in the bubble bar invisible, except for the bubble that's being animated.
- setBackground(null);
- for (int i = 0; i < getChildCount(); i++) {
- final BubbleView view = (BubbleView) getChildAt(i);
- final String key = view.getBubble().getKey();
- if (!bubbleKey.equals(key)) {
- view.setVisibility(INVISIBLE);
- }
- }
- setVisibility(VISIBLE);
- setAlpha(1);
- setTranslationY(0);
- setScaleX(1);
- setScaleY(1);
}
- /** Resets the state after the bubble animation completed. */
+ /** Notifies the bubble bar that a new bubble animation is complete. */
public void onAnimatingBubbleCompleted() {
mIsAnimatingNewBubble = false;
- // setting the background triggers relayout so no need to explicitly invalidate after the
- // animation
- setBackground(mBubbleBarBackground);
- for (int i = 0; i < getChildCount(); i++) {
- final BubbleView view = (BubbleView) getChildAt(i);
- view.setVisibility(VISIBLE);
- view.setAlpha(1f);
- }
}
// TODO: (b/280605790) animate it
@@ -577,7 +532,7 @@
private void updateLayoutParams() {
LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
- lp.height = getBubbleBarHeight();
+ lp.height = (int) getBubbleBarExpandedHeight();
lp.width = (int) (mIsBarExpanded ? expandedWidth() : collapsedWidth());
setLayoutParams(lp);
}
@@ -593,12 +548,6 @@
* on the expanded state.
*/
private void updateChildrenRenderNodeProperties() {
- if (mIsAnimatingNewBubble) {
- // don't update bubbles if a new bubble animation is playing.
- // the bubble bar will redraw itself via onLayout after the animation.
- return;
- }
-
final float widthState = (float) mWidthAnimator.getAnimatedValue();
final float currentWidth = getWidth();
final float expandedWidth = expandedWidth();
@@ -864,8 +813,13 @@
: mIconSize + horizontalPadding;
}
- private int getBubbleBarHeight() {
- return (int) (mIconSize + mBubbleBarPadding * 2 + mPointerSize);
+ private float getBubbleBarExpandedHeight() {
+ return getBubbleBarCollapsedHeight() + mPointerSize;
+ }
+
+ float getBubbleBarCollapsedHeight() {
+ // the pointer is invisible when collapsed
+ return mIconSize + mBubbleBarPadding * 2;
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index 3c46f32..0b92748 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -189,6 +189,10 @@
return mBubbleBarTranslationY;
}
+ float getBubbleBarCollapsedHeight() {
+ return mBarView.getBubbleBarCollapsedHeight();
+ }
+
/**
* Whether the bubble bar is visible or not.
*/
@@ -222,10 +226,9 @@
return mBarView.getBubbleBarBounds();
}
- /** The bounds of the animating bubble, or {@code null} if no bubble is animating. */
- @Nullable
- public Rect getAnimatingBubbleBounds() {
- return mBarView.getAnimatingBubbleBounds();
+ /** Whether a new bubble is animating. */
+ public boolean isAnimatingNewBubble() {
+ return mBarView.isAnimatingNewBubble();
}
/** The horizontal margin of the bubble bar from the edge of the screen. */
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
index bea0af8..f689a05 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
@@ -25,6 +25,7 @@
import android.view.MotionEvent;
import android.view.View;
+import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.taskbar.StashedHandleViewController;
import com.android.launcher3.taskbar.TaskbarActivityContext;
@@ -77,11 +78,16 @@
private boolean mBubblesShowingOnOverview;
private boolean mIsSysuiLocked;
+ private final float mHandleCenterFromScreenBottom;
+
@Nullable
private AnimatorSet mAnimator;
public BubbleStashController(TaskbarActivityContext activity) {
mActivity = activity;
+ // the handle is centered within the stashed taskbar area
+ mHandleCenterFromScreenBottom =
+ mActivity.getResources().getDimensionPixelSize(R.dimen.bubblebar_stashed_size) / 2f;
}
public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) {
@@ -266,7 +272,6 @@
*/
private AnimatorSet createStashAnimator(boolean isStashed, long duration) {
AnimatorSet animatorSet = new AnimatorSet();
- final float stashTranslation = (mUnstashedHeight - mStashedHeight) / 2f;
AnimatorSet fullLengthAnimatorSet = new AnimatorSet();
// Not exactly half and may overlap. See [first|second]HalfDurationScale below.
@@ -280,7 +285,8 @@
firstHalfDurationScale = 0.75f;
secondHalfDurationScale = 0.5f;
- fullLengthAnimatorSet.play(mIconTranslationYForStash.animateToValue(stashTranslation));
+ fullLengthAnimatorSet.play(
+ mIconTranslationYForStash.animateToValue(getStashTranslation()));
firstHalfAnimatorSet.playTogether(
mIconAlphaForStash.animateToValue(0),
@@ -329,6 +335,10 @@
return animatorSet;
}
+ private float getStashTranslation() {
+ return (mUnstashedHeight - mStashedHeight) / 2f;
+ }
+
private void onIsStashedChanged() {
mControllers.runAfterInit(() -> {
mHandleViewController.onIsStashedChanged();
@@ -336,7 +346,7 @@
});
}
- private float getBubbleBarTranslationYForTaskbar() {
+ public float getBubbleBarTranslationYForTaskbar() {
return -mActivity.getDeviceProfile().taskbarBottomMargin;
}
@@ -355,6 +365,25 @@
: getBubbleBarTranslationYForTaskbar();
}
+ /**
+ * The difference on the Y axis between the center of the handle and the center of the bubble
+ * bar.
+ */
+ public float getDiffBetweenHandleAndBarCenters() {
+ // the difference between the centers of the handle and the bubble bar is the difference
+ // between their distance from the bottom of the screen.
+
+ float barCenter = mBarViewController.getBubbleBarCollapsedHeight() / 2f;
+ return mHandleCenterFromScreenBottom - barCenter;
+ }
+
+ /** The distance the handle moves as part of the new bubble animation. */
+ public float getStashedHandleTranslationForNewBubbleAnimation() {
+ // the should move up to the top of the stashed taskbar area. it is centered within it so
+ // it should move the same distance as it is away from the bottom.
+ return -mHandleCenterFromScreenBottom;
+ }
+
/** Checks whether the motion event is over the stash handle. */
public boolean isEventOverStashHandle(MotionEvent ev) {
return mHandleViewController.isEventOverHandle(ev);
@@ -365,11 +394,6 @@
mHandleViewController.setBubbleBarLocation(bubbleBarLocation);
}
- /** Returns the x position of the center of the stashed handle. */
- public float getStashedHandleCenterX() {
- return mHandleViewController.getStashedHandleCenterX();
- }
-
/** Returns the [PhysicsAnimator] for the stashed handle view. */
public PhysicsAnimator<View> getStashedHandlePhysicsAnimator() {
return mHandleViewController.getPhysicsAnimator();
@@ -389,4 +413,14 @@
mIsStashed = false;
onIsStashedChanged();
}
+
+ /** Stashes the bubble bar immediately without animation. */
+ public void stashBubbleBarImmediate() {
+ mHandleViewController.setTranslationYForSwipe(0);
+ mIconAlphaForStash.setValue(0);
+ mIconTranslationYForStash.updateValue(getStashTranslation());
+ mIconScaleForStash.updateValue(STASHED_BAR_SCALE);
+ mIsStashed = true;
+ onIsStashedChanged();
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
index 6f1a093..91103d7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
@@ -251,11 +251,6 @@
return mStashedHandleAlpha;
}
- /** Returns the x position of the center of the stashed handle. */
- public float getStashedHandleCenterX() {
- return mStashedHandleBounds.exactCenterX();
- }
-
/**
* Creates and returns an Animator that updates the stashed handle shape and size.
* When stashed, the shape is a thin rounded pill. When unstashed, the shape morphs into
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
index da36944..a6d0ff8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
@@ -40,20 +40,8 @@
private companion object {
/** The time to show the flyout. */
const val FLYOUT_DELAY_MS: Long = 2500
- /** The translation Y the new bubble will animate to. */
- const val BUBBLE_ANIMATION_BUBBLE_TRANSLATION_Y = -50f
- /** The initial translation Y value the new bubble is set to before the animation starts. */
- // TODO(liranb): get rid of this and calculate this based on the y-distance between the
- // bubble and the stash handle.
- const val BUBBLE_ANIMATION_TRANSLATION_Y_OFFSET = 50f
/** The initial scale Y value that the new bubble is set to before the animation starts. */
const val BUBBLE_ANIMATION_INITIAL_SCALE_Y = 0.3f
- /**
- * The distance the stashed handle will travel as it gets hidden as part of the new bubble
- * animation.
- */
- // TODO(liranb): calculate this based on the position of the views
- const val BUBBLE_ANIMATION_STASH_HANDLE_TRANSLATION_Y = -20f
}
/** Wrapper around the animating bubble with its show and hide animations. */
@@ -105,166 +93,156 @@
if (animator.isRunning()) animator.cancel()
// the animation of a new bubble is divided into 2 parts. The first part shows the bubble
// and the second part hides it after a delay.
- val showAnimation = buildShowAnimation(bubbleView, b.key)
- val hideAnimation = buildHideAnimation(bubbleView)
+ val showAnimation = buildShowAnimation()
+ val hideAnimation = buildHideAnimation()
animatingBubble = AnimatingBubble(bubbleView, showAnimation, hideAnimation)
scheduler.post(showAnimation)
scheduler.postDelayed(FLYOUT_DELAY_MS, hideAnimation)
}
/**
- * Returns a lambda that starts the animation that shows the new bubble.
+ * Returns a [Runnable] that starts the animation that shows the new or updated bubble.
*
* Visually, the animation is divided into 2 parts. The stash handle starts animating up and
- * fading out and then the bubble starts animating up and fading in.
+ * fading out and then the bubble bar starts animating up and fading in.
*
- * To make the transition from the handle to the bubble smooth, the positions and movement of
- * the 2 views must be synchronized. To do that we use a single spring path along the Y axis,
- * starting from the handle's position to the eventual bubble's position. The path is split into
- * 3 parts.
+ * To make the transition from the handle to the bar smooth, the positions and movement of the 2
+ * views must be synchronized. To do that we use a single spring path along the Y axis, starting
+ * from the handle's position to the eventual bar's position. The path is split into 3 parts.
* 1. In the first part, we only animate the handle.
- * 1. In the second part the handle is fully hidden, and the bubble is animating in.
- * 1. The third part is the overshoot of the spring animation, where we make the bubble fully
+ * 2. In the second part the handle is fully hidden, and the bubble bar is animating in.
+ * 3. The third part is the overshoot of the spring animation, where we make the bubble fully
* visible which helps avoiding further updates when we re-enter the second part.
*/
- private fun buildShowAnimation(
- bubbleView: BubbleView,
- key: String,
- ) = Runnable {
- bubbleBarView.prepareForAnimatingBubbleWhileStashed(key)
- // calculate the initial translation x the bubble should have in order to align it with the
- // stash handle.
- val initialTranslationX =
- bubbleStashController.stashedHandleCenterX - bubbleView.centerXOnScreen
- // prepare the bubble for the animation
- bubbleView.alpha = 0f
- bubbleView.translationX = initialTranslationX
- bubbleView.scaleY = BUBBLE_ANIMATION_INITIAL_SCALE_Y
- bubbleView.visibility = VISIBLE
+ private fun buildShowAnimation() = Runnable {
+ // prepare the bubble bar for the animation
+ bubbleBarView.onAnimatingBubbleStarted()
+ bubbleBarView.visibility = VISIBLE
+ bubbleBarView.alpha = 0f
+ bubbleBarView.translationY = 0f
+ bubbleBarView.scaleX = 1f
+ bubbleBarView.scaleY = BUBBLE_ANIMATION_INITIAL_SCALE_Y
+ bubbleBarView.relativePivotY = 0.5f
+
+ // this is the offset between the center of the bubble bar and the center of the stash
+ // handle. when the handle becomes invisible and we start animating in the bubble bar,
+ // the translation y is offset by this value to make the transition from the handle to the
+ // bar smooth.
+ val offset = bubbleStashController.diffBetweenHandleAndBarCenters
+ val stashedHandleTranslationY =
+ bubbleStashController.stashedHandleTranslationForNewBubbleAnimation
// this is the total distance that both the stashed handle and the bubble will be traveling
- val totalTranslationY =
- BUBBLE_ANIMATION_BUBBLE_TRANSLATION_Y + BUBBLE_ANIMATION_STASH_HANDLE_TRANSLATION_Y
+ // at the end of the animation the bubble bar will be positioned in the same place when it
+ // shows while we're in an app.
+ val totalTranslationY = bubbleStashController.bubbleBarTranslationYForTaskbar + offset
val animator = bubbleStashController.stashedHandlePhysicsAnimator
animator.setDefaultSpringConfig(springConfig)
animator.spring(DynamicAnimation.TRANSLATION_Y, totalTranslationY)
- animator.addUpdateListener { target, values ->
+ animator.addUpdateListener { handle, values ->
val ty = values[DynamicAnimation.TRANSLATION_Y]?.value ?: return@addUpdateListener
when {
- ty >= BUBBLE_ANIMATION_STASH_HANDLE_TRANSLATION_Y -> {
+ ty >= stashedHandleTranslationY -> {
// we're in the first leg of the animation. only animate the handle. the bubble
- // remains hidden during this part of the animation
+ // bar remains hidden during this part of the animation
- // map the path [0, BUBBLE_ANIMATION_STASH_HANDLE_TRANSLATION_Y] to [0,1]
- val fraction = ty / BUBBLE_ANIMATION_STASH_HANDLE_TRANSLATION_Y
- target.alpha = 1 - fraction
+ // map the path [0, stashedHandleTranslationY] to [0,1]
+ val fraction = ty / stashedHandleTranslationY
+ handle.alpha = 1 - fraction
}
ty >= totalTranslationY -> {
// this is the second leg of the animation. the handle should be completely
- // hidden and the bubble should start animating in.
+ // hidden and the bubble bar should start animating in.
// it's possible that we're re-entering this leg because this is a spring
- // animation, so only set the alpha and scale for the bubble if we didn't
+ // animation, so only set the alpha and scale for the bubble bar if we didn't
// already fully animate in.
- target.alpha = 0f
- bubbleView.translationY = ty + BUBBLE_ANIMATION_TRANSLATION_Y_OFFSET
- if (bubbleView.alpha != 1f) {
- // map the path
- // [BUBBLE_ANIMATION_STASH_HANDLE_TRANSLATION_Y, totalTranslationY]
- // to [0, 1]
+ handle.alpha = 0f
+ bubbleBarView.translationY = ty - offset
+ if (bubbleBarView.alpha != 1f) {
+ // map the path [stashedHandleTranslationY, totalTranslationY] to [0, 1]
val fraction =
- (ty - BUBBLE_ANIMATION_STASH_HANDLE_TRANSLATION_Y) /
- BUBBLE_ANIMATION_BUBBLE_TRANSLATION_Y
- bubbleView.alpha = fraction
- bubbleView.scaleY =
+ (ty - stashedHandleTranslationY) /
+ (totalTranslationY - stashedHandleTranslationY)
+ bubbleBarView.alpha = fraction
+ bubbleBarView.scaleY =
BUBBLE_ANIMATION_INITIAL_SCALE_Y +
(1 - BUBBLE_ANIMATION_INITIAL_SCALE_Y) * fraction
}
}
else -> {
// we're past the target animated value, set the alpha and scale for the bubble
- // so that it's fully visible and no longer changing, but keep moving it along
- // the animation path
- bubbleView.alpha = 1f
- bubbleView.scaleY = 1f
- bubbleView.translationY = ty + BUBBLE_ANIMATION_TRANSLATION_Y_OFFSET
+ // bar so that it's fully visible and no longer changing, but keep moving it
+ // along the animation path
+ bubbleBarView.alpha = 1f
+ bubbleBarView.scaleY = 1f
+ bubbleBarView.translationY = ty - offset
}
}
}
animator.addEndListener { _, _, _, _, _, _, _ ->
- // the bubble is now fully settled in. make it touchable
- bubbleBarView.updateAnimatingBubbleBounds(
- bubbleView.left,
- bubbleView.top,
- bubbleView.width,
- bubbleView.height
- )
+ // the bubble bar is now fully settled in. update taskbar touch region so it's touchable
bubbleStashController.updateTaskbarTouchRegion()
}
animator.start()
}
/**
- * Returns a lambda that starts the animation that hides the new bubble.
+ * Returns a [Runnable] that starts the animation that hides the bubble bar.
*
* Similarly to the show animation, this is visually divided into 2 parts. We first animate the
- * bubble out, and then animate the stash handle in. At the end of the animation we reset the
- * values of the bubble.
+ * bubble bar out, and then animate the stash handle in. At the end of the animation we reset
+ * values of the bubble bar.
*
* This is a spring animation that goes along the same path of the show animation in the
* opposite order, and is split into 3 parts:
* 1. In the first part the bubble animates out.
- * 1. In the second part the bubble is fully hidden and the handle animates in.
- * 1. The third part is the overshoot. The handle is made fully visible.
+ * 2. In the second part the bubble bar is fully hidden and the handle animates in.
+ * 3. The third part is the overshoot. The handle is made fully visible.
*/
- private fun buildHideAnimation(bubbleView: BubbleView) = Runnable {
- // this is the total distance that both the stashed handle and the bubble will be traveling
- val totalTranslationY =
- BUBBLE_ANIMATION_BUBBLE_TRANSLATION_Y + BUBBLE_ANIMATION_STASH_HANDLE_TRANSLATION_Y
+ private fun buildHideAnimation() = Runnable {
+ val offset = bubbleStashController.diffBetweenHandleAndBarCenters
+ val stashedHandleTranslationY =
+ bubbleStashController.stashedHandleTranslationForNewBubbleAnimation
+ // this is the total distance that both the stashed handle and the bar will be traveling
+ val totalTranslationY = bubbleStashController.bubbleBarTranslationYForTaskbar + offset
val animator = bubbleStashController.stashedHandlePhysicsAnimator
animator.setDefaultSpringConfig(springConfig)
animator.spring(DynamicAnimation.TRANSLATION_Y, 0f)
- animator.addUpdateListener { target, values ->
+ animator.addUpdateListener { handle, values ->
val ty = values[DynamicAnimation.TRANSLATION_Y]?.value ?: return@addUpdateListener
when {
- ty <= BUBBLE_ANIMATION_STASH_HANDLE_TRANSLATION_Y -> {
- // this is the first leg of the animation. only animate the bubble. the handle
- // is hidden during this part
- bubbleView.translationY = ty + BUBBLE_ANIMATION_TRANSLATION_Y_OFFSET
- // map the path
- // [totalTranslationY, BUBBLE_ANIMATION_STASH_HANDLE_TRANSLATION_Y]
- // to [0, 1]
- val fraction = (totalTranslationY - ty) / BUBBLE_ANIMATION_BUBBLE_TRANSLATION_Y
- bubbleView.alpha = 1 - fraction / 2
- bubbleView.scaleY = 1 - (1 - BUBBLE_ANIMATION_INITIAL_SCALE_Y) * fraction
+ ty <= stashedHandleTranslationY -> {
+ // this is the first leg of the animation. only animate the bubble bar. the
+ // handle is hidden during this part
+ bubbleBarView.translationY = ty - offset
+ // map the path [totalTranslationY, stashedHandleTranslationY] to [0, 1]
+ val fraction =
+ (totalTranslationY - ty) / (totalTranslationY - stashedHandleTranslationY)
+ bubbleBarView.alpha = 1 - fraction
+ bubbleBarView.scaleY = 1 - (1 - BUBBLE_ANIMATION_INITIAL_SCALE_Y) * fraction
}
ty <= 0 -> {
- // this is the second part of the animation. make the bubble invisible and
+ // this is the second part of the animation. make the bubble bar invisible and
// start fading in the handle, but don't update the alpha if it's already fully
// visible
- bubbleView.alpha = 0f
- if (target.alpha != 1f) {
- // map the path [BUBBLE_ANIMATION_STASH_HANDLE_TRANSLATION_Y, 0] to [0, 1]
- val fraction =
- (BUBBLE_ANIMATION_STASH_HANDLE_TRANSLATION_Y - ty) /
- BUBBLE_ANIMATION_STASH_HANDLE_TRANSLATION_Y
- target.alpha = fraction
+ bubbleBarView.alpha = 0f
+ if (handle.alpha != 1f) {
+ // map the path [stashedHandleTranslationY, 0] to [0, 1]
+ val fraction = (stashedHandleTranslationY - ty) / stashedHandleTranslationY
+ handle.alpha = fraction
}
}
else -> {
// we reached the target value. set the alpha of the handle to 1
- target.alpha = 1f
+ handle.alpha = 1f
}
}
}
animator.addEndListener { _, _, _, _, _, _, _ ->
animatingBubble = null
- bubbleView.alpha = 0f
- bubbleView.translationY = 0f
- bubbleView.scaleY = 1f
- if (bubbleStashController.isStashed) {
- bubbleBarView.alpha = 0f
- }
+ bubbleStashController.stashBubbleBarImmediate()
bubbleBarView.onAnimatingBubbleCompleted()
+ bubbleBarView.relativePivotY = 1f
bubbleStashController.updateTaskbarTouchRegion()
}
animator.start()
@@ -275,14 +253,7 @@
val hideAnimation = animatingBubble?.hideAnimation ?: return
scheduler.cancel(hideAnimation)
bubbleBarView.onAnimatingBubbleCompleted()
+ bubbleBarView.relativePivotY = 1f
animatingBubble = null
}
}
-
-/** The X position in screen coordinates of the center of the bubble. */
-private val BubbleView.centerXOnScreen: Float
- get() {
- val screenCoordinates = IntArray(2)
- getLocationOnScreen(screenCoordinates)
- return screenCoordinates[0] + width / 2f
- }
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
index 3d8484d..f46fdac 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
@@ -22,7 +22,6 @@
import android.graphics.drawable.ColorDrawable
import android.view.LayoutInflater
import android.view.View
-import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import android.widget.FrameLayout
import androidx.core.graphics.drawable.toBitmap
@@ -45,6 +44,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@SmallTest
@@ -87,6 +87,12 @@
val bubbleStashController = mock<BubbleStashController>()
whenever(bubbleStashController.isStashed).thenReturn(true)
+ whenever(bubbleStashController.diffBetweenHandleAndBarCenters)
+ .thenReturn(DIFF_BETWEEN_HANDLE_AND_BAR_CENTERS)
+ whenever(bubbleStashController.stashedHandleTranslationForNewBubbleAnimation)
+ .thenReturn(HANDLE_TRANSLATION)
+ whenever(bubbleStashController.bubbleBarTranslationYForTaskbar)
+ .thenReturn(BAR_TRANSLATION_Y_FOR_TASKBAR)
val handle = View(context)
val handleAnimator = PhysicsAnimator.getInstance(handle)
@@ -104,13 +110,13 @@
PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
assertThat(handle.alpha).isEqualTo(0)
- assertThat(handle.translationY).isEqualTo(-70)
- assertThat(overflowView.visibility).isEqualTo(INVISIBLE)
+ assertThat(handle.translationY)
+ .isEqualTo(DIFF_BETWEEN_HANDLE_AND_BAR_CENTERS + BAR_TRANSLATION_Y_FOR_TASKBAR)
assertThat(bubbleBarView.visibility).isEqualTo(VISIBLE)
- assertThat(bubbleView.visibility).isEqualTo(VISIBLE)
- assertThat(bubbleView.alpha).isEqualTo(1)
- assertThat(bubbleView.translationY).isEqualTo(-20)
- assertThat(bubbleView.scaleY).isEqualTo(1)
+ assertThat(bubbleBarView.scaleX).isEqualTo(1)
+ assertThat(bubbleBarView.scaleY).isEqualTo(1)
+ assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_TASKBAR)
+ assertThat(bubbleBarView.isAnimatingNewBubble).isTrue()
// execute the hide bubble animation
assertThat(animatorScheduler.delayedBlock).isNotNull()
@@ -120,14 +126,11 @@
InstrumentationRegistry.getInstrumentation().waitForIdleSync()
PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
- assertThat(bubbleView.alpha).isEqualTo(1)
- assertThat(bubbleView.visibility).isEqualTo(VISIBLE)
- assertThat(bubbleView.translationY).isEqualTo(0)
- assertThat(bubbleBarView.alpha).isEqualTo(0)
- assertThat(overflowView.alpha).isEqualTo(1)
- assertThat(overflowView.visibility).isEqualTo(VISIBLE)
assertThat(handle.alpha).isEqualTo(1)
assertThat(handle.translationY).isEqualTo(0)
+ assertThat(bubbleBarView.alpha).isEqualTo(0)
+ assertThat(bubbleBarView.isAnimatingNewBubble).isFalse()
+ verify(bubbleStashController).stashBubbleBarImmediate()
}
@Test
@@ -158,6 +161,12 @@
val bubbleStashController = mock<BubbleStashController>()
whenever(bubbleStashController.isStashed).thenReturn(true)
+ whenever(bubbleStashController.diffBetweenHandleAndBarCenters)
+ .thenReturn(DIFF_BETWEEN_HANDLE_AND_BAR_CENTERS)
+ whenever(bubbleStashController.stashedHandleTranslationForNewBubbleAnimation)
+ .thenReturn(HANDLE_TRANSLATION)
+ whenever(bubbleStashController.bubbleBarTranslationYForTaskbar)
+ .thenReturn(BAR_TRANSLATION_Y_FOR_TASKBAR)
val handle = View(context)
val handleAnimator = PhysicsAnimator.getInstance(handle)
@@ -175,13 +184,15 @@
PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
assertThat(handle.alpha).isEqualTo(0)
- assertThat(handle.translationY).isEqualTo(-70)
- assertThat(overflowView.visibility).isEqualTo(INVISIBLE)
+ assertThat(handle.translationY)
+ .isEqualTo(DIFF_BETWEEN_HANDLE_AND_BAR_CENTERS + BAR_TRANSLATION_Y_FOR_TASKBAR)
assertThat(bubbleBarView.visibility).isEqualTo(VISIBLE)
- assertThat(bubbleView.visibility).isEqualTo(VISIBLE)
- assertThat(bubbleView.alpha).isEqualTo(1)
- assertThat(bubbleView.translationY).isEqualTo(-20)
- assertThat(bubbleView.scaleY).isEqualTo(1)
+ assertThat(bubbleBarView.scaleX).isEqualTo(1)
+ assertThat(bubbleBarView.scaleY).isEqualTo(1)
+ assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_TASKBAR)
+ assertThat(bubbleBarView.isAnimatingNewBubble).isTrue()
+
+ verify(bubbleStashController).updateTaskbarTouchRegion()
// verify the hide bubble animation is pending
assertThat(animatorScheduler.delayedBlock).isNotNull()
@@ -189,11 +200,9 @@
animator.onBubbleClickedWhileAnimating()
assertThat(animatorScheduler.delayedBlock).isNull()
- assertThat(overflowView.visibility).isEqualTo(VISIBLE)
- assertThat(overflowView.alpha).isEqualTo(1)
- assertThat(bubbleView.alpha).isEqualTo(1)
- assertThat(bubbleView.visibility).isEqualTo(VISIBLE)
- assertThat(bubbleBarView.background).isNotNull()
+ assertThat(bubbleBarView.alpha).isEqualTo(1)
+ assertThat(bubbleBarView.visibility).isEqualTo(VISIBLE)
+ assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_TASKBAR)
assertThat(bubbleBarView.isAnimatingNewBubble).isFalse()
}
@@ -217,3 +226,7 @@
}
}
}
+
+private const val DIFF_BETWEEN_HANDLE_AND_BAR_CENTERS = -20f
+private const val HANDLE_TRANSLATION = -30f
+private const val BAR_TRANSLATION_Y_FOR_TASKBAR = -50f