From 29a3e5d676e470b5e21a77ee44fe09c6d669e830 Mon Sep 17 00:00:00 2001 From: Liran Binyamin Date: Fri, 26 Apr 2024 15:17:27 -0400 Subject: [PATCH] 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 --- .../taskbar/TaskbarInsetsController.kt | 11 +- .../taskbar/bubbles/BubbleBarView.java | 82 ++------ .../bubbles/BubbleBarViewController.java | 11 +- .../bubbles/BubbleStashController.java | 50 ++++- .../BubbleStashedHandleViewController.java | 5 - .../animation/BubbleBarViewAnimator.kt | 193 ++++++++---------- .../animation/BubbleBarViewAnimatorTest.kt | 61 +++--- 7 files changed, 193 insertions(+), 220 deletions(-) diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt index 95c4e25dd8..4a8ed87d19 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt @@ -155,13 +155,16 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas ) // 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 52349369ef..8c6cbc9230 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 @@ public class BubbleBarView extends FrameLayout { * 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 @@ public class BubbleBarView extends FrameLayout { setClipToPadding(false); - mBubbleBarBackground = new BubbleBarBackground(context, getBubbleBarHeight()); + mBubbleBarBackground = new BubbleBarBackground(context, getBubbleBarExpandedHeight()); setBackgroundDrawable(mBubbleBarBackground); mWidthAnimator.setDuration(WIDTH_ANIMATION_DURATION_MS); @@ -246,7 +244,7 @@ public class BubbleBarView extends FrameLayout { params.width = (int) mIconSize; childView.setLayoutParams(params); } - mBubbleBarBackground.setHeight(getBubbleBarHeight()); + mBubbleBarBackground.setHeight(getBubbleBarExpandedHeight()); updateLayoutParams(); } @@ -462,30 +460,6 @@ public class BubbleBarView extends FrameLayout { 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. - * - *

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 @@ public class BubbleBarView extends FrameLayout { 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 @@ public class BubbleBarView extends FrameLayout { 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 @@ public class BubbleBarView extends FrameLayout { 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 @@ public class BubbleBarView extends FrameLayout { * 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 @@ public class BubbleBarView extends FrameLayout { : 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 3c46f32e17..0b927488f3 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 @@ public class BubbleBarViewController { return mBubbleBarTranslationY; } + float getBubbleBarCollapsedHeight() { + return mBarView.getBubbleBarCollapsedHeight(); + } + /** * Whether the bubble bar is visible or not. */ @@ -222,10 +226,9 @@ public class BubbleBarViewController { 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 bea0af8394..f689a05e7f 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.InsetsController; 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 @@ public class BubbleStashController { 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 @@ public class BubbleStashController { */ 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 @@ public class BubbleStashController { 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 @@ public class BubbleStashController { return animatorSet; } + private float getStashTranslation() { + return (mUnstashedHeight - mStashedHeight) / 2f; + } + private void onIsStashedChanged() { mControllers.runAfterInit(() -> { mHandleViewController.onIsStashedChanged(); @@ -336,7 +346,7 @@ public class BubbleStashController { }); } - private float getBubbleBarTranslationYForTaskbar() { + public float getBubbleBarTranslationYForTaskbar() { return -mActivity.getDeviceProfile().taskbarBottomMargin; } @@ -355,6 +365,25 @@ public class BubbleStashController { : 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 @@ public class BubbleStashController { 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 getStashedHandlePhysicsAnimator() { return mHandleViewController.getPhysicsAnimator(); @@ -389,4 +413,14 @@ public class BubbleStashController { 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 6f1a0934a7..91103d75bd 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 @@ public class BubbleStashedHandleViewController { 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 da369448da..a6d0ff8a2b 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 @@ constructor( 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 @@ constructor( 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 @@ constructor( 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 3d8484d2d1..f46fdac001 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.Path 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.Before 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 @@ class BubbleBarViewAnimatorTest { val bubbleStashController = mock() 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 @@ class BubbleBarViewAnimatorTest { 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 @@ class BubbleBarViewAnimatorTest { 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 @@ class BubbleBarViewAnimatorTest { val bubbleStashController = mock() 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 @@ class BubbleBarViewAnimatorTest { 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 @@ class BubbleBarViewAnimatorTest { 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 @@ class BubbleBarViewAnimatorTest { } } } + +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