From e79120af38c7576d72090a738ac48312e48c0678 Mon Sep 17 00:00:00 2001 From: Liran Binyamin Date: Thu, 11 Apr 2024 11:34:47 -0400 Subject: [PATCH] Update new bubble animation This change is a rewrite of the new bubble animation. Previously we chained the handle animation and the bubble animation so that they run sequentially. That caused the transition from the handle to the bubble to be jarring. We now use a single spring animation along the y axis that starts from the position of the handle to the position of where the bubble will end up. The animation is split into 3 logical parts, where initially the bubble animates out, then the bubble starts animating in, and the last part is the overshoot of the spring animation, which is used to mark the bubble fully visible and ensure that from that point on, the bubble is only moving but doesn't change in scale or transparency as the bounce effect is playing. Using a single animation path allows for a smooth transition from the handle to the bubble view. Demo: http://recall/-/bJtug1HhvXkkeA4MQvIaiP/hHNUBdNJPiWi9gMbqy45UJ Flag: ACONFIG com.android.wm.shell.enable_bubble_bar DEVELOPMENT Bug: 280605846 Test: atest BubbleBarViewAnimatorTest Change-Id: Ic1d3244574b8500d4aad2e9c718e61c1c34bd82a --- .../bubbles/BubbleStashController.java | 40 +--- .../BubbleStashedHandleViewController.java | 25 +-- .../animation/BubbleBarViewAnimator.kt | 174 ++++++++++++------ .../animation/BubbleBarViewAnimatorTest.kt | 72 ++------ 4 files changed, 147 insertions(+), 164 deletions(-) diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java index 61a2e2222d..76d86de4e2 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java @@ -23,6 +23,7 @@ import android.animation.AnimatorSet; import android.annotation.Nullable; import android.view.InsetsController; import android.view.MotionEvent; +import android.view.View; import com.android.launcher3.anim.AnimatedFloat; import com.android.launcher3.taskbar.StashedHandleViewController; @@ -32,6 +33,7 @@ import com.android.launcher3.taskbar.TaskbarInsetsController; import com.android.launcher3.taskbar.TaskbarStashController; import com.android.launcher3.util.MultiPropertyFactory; import com.android.wm.shell.common.bubbles.BubbleBarLocation; +import com.android.wm.shell.shared.animation.PhysicsAnimator; /** * Coordinates between controllers such as BubbleBarView and BubbleHandleViewController to @@ -51,15 +53,6 @@ public class BubbleStashController { */ private static final float STASHED_BAR_SCALE = 0.5f; - /** The duration of hiding and showing the stashed handle as part of a new bubble animation. */ - private static final long NEW_BUBBLE_HANDLE_ANIMATION_DURATION_MS = 200; - - /** The translation Y value the handle animates to when hiding it for a new bubble. */ - private static final int NEW_BUBBLE_HIDE_HANDLE_ANIMATION_TRANSLATION_Y = -20; - - /** The alpha value the handle animates to when hiding it for a new bubble. */ - public static final float NEW_BUBBLE_HIDE_HANDLE_ANIMATION_ALPHA = 0.5f; - protected final TaskbarActivityContext mActivity; // Initialized in init. @@ -73,7 +66,6 @@ public class BubbleStashController { private AnimatedFloat mIconScaleForStash; private AnimatedFloat mIconTranslationYForStash; private MultiPropertyFactory.MultiProperty mBubbleStashedHandleAlpha; - private AnimatedFloat mBubbleStashedHandleTranslationY; private boolean mRequestedStashState; private boolean mRequestedExpandedState; @@ -105,7 +97,6 @@ public class BubbleStashController { mBubbleStashedHandleAlpha = mHandleViewController.getStashedHandleAlpha().get( StashedHandleViewController.ALPHA_INDEX_STASHED); - mBubbleStashedHandleTranslationY = mHandleViewController.getStashedHandleTranslationY(); mStashedHeight = mHandleViewController.getStashedHeight(); mUnstashedHeight = mHandleViewController.getUnstashedHeight(); @@ -379,29 +370,8 @@ public class BubbleStashController { return mHandleViewController.getStashedHandleCenterX(); } - /** Returns the animation for hiding the handle before a new bubble animates in. */ - public AnimatorSet buildHideHandleAnimationForNewBubble() { - AnimatorSet animatorSet = new AnimatorSet(); - animatorSet.playTogether( - mBubbleStashedHandleTranslationY.animateToValue( - NEW_BUBBLE_HIDE_HANDLE_ANIMATION_TRANSLATION_Y), - mBubbleStashedHandleAlpha.animateToValue(NEW_BUBBLE_HIDE_HANDLE_ANIMATION_ALPHA)); - animatorSet.setDuration(NEW_BUBBLE_HANDLE_ANIMATION_DURATION_MS); - return animatorSet; - } - - /** Sets the alpha value of the stashed handle. */ - public void setStashAlpha(float alpha) { - mBubbleStashedHandleAlpha.setValue(alpha); - } - - /** Returns the animation for showing the handle after a new bubble animated in. */ - public AnimatorSet buildShowHandleAnimationForNewBubble() { - AnimatorSet animatorSet = new AnimatorSet(); - animatorSet.playTogether( - mBubbleStashedHandleTranslationY.animateToValue(0), - mBubbleStashedHandleAlpha.animateToValue(1)); - animatorSet.setDuration(NEW_BUBBLE_HANDLE_ANIMATION_DURATION_MS); - return animatorSet; + /** Returns the [PhysicsAnimator] for the stashed handle view. */ + public PhysicsAnimator getStashedHandlePhysicsAnimator() { + return mHandleViewController.getPhysicsAnimator(); } } diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java index 2a5912a5c2..6f1a0934a7 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java @@ -29,7 +29,6 @@ import android.view.View; import android.view.ViewOutlineProvider; import com.android.launcher3.R; -import com.android.launcher3.anim.AnimatedFloat; import com.android.launcher3.anim.RevealOutlineAnimation; import com.android.launcher3.anim.RoundedRectRevealOutlineProvider; import com.android.launcher3.taskbar.StashedHandleView; @@ -40,6 +39,7 @@ import com.android.launcher3.util.MultiPropertyFactory; import com.android.launcher3.util.MultiValueAlpha; import com.android.systemui.shared.navigationbar.RegionSamplingHelper; import com.android.wm.shell.common.bubbles.BubbleBarLocation; +import com.android.wm.shell.shared.animation.PhysicsAnimator; /** * Handles properties/data collection, then passes the results to our stashed handle View to render. @@ -59,12 +59,6 @@ public class BubbleStashedHandleViewController { private int mStashedHandleWidth; private int mStashedHandleHeight; - private final AnimatedFloat mStashedHandleTranslationY = - new AnimatedFloat(this::updateTranslationY); - - // Modified when swipe up is happening on the stashed handle or task bar. - private float mSwipeUpTranslationY; - // The bounds we want to clip to in the settled state when showing the stashed handle. private final Rect mStashedHandleBounds = new Rect(); @@ -129,6 +123,11 @@ public class BubbleStashedHandleViewController { updateBounds(mBarViewController.getBubbleBarLocation())); } + /** Returns the [PhysicsAnimator] for the stashed handle view. */ + public PhysicsAnimator getPhysicsAnimator() { + return PhysicsAnimator.getInstance(mStashedHandleView); + } + private void updateBounds(BubbleBarLocation bubbleBarLocation) { // As more bubbles get added, the icon bounds become larger. To ensure a consistent // handle bar position, we pin it to the edge of the screen. @@ -238,21 +237,11 @@ public class BubbleStashedHandleViewController { } } - /** Returns an animator for translation Y. */ - public AnimatedFloat getStashedHandleTranslationY() { - return mStashedHandleTranslationY; - } - /** * Sets the translation of the stashed handle during the swipe up gesture. */ public void setTranslationYForSwipe(float transY) { - mSwipeUpTranslationY = transY; - updateTranslationY(); - } - - private void updateTranslationY() { - mStashedHandleView.setTranslationY(mStashedHandleTranslationY.value + mSwipeUpTranslationY); + mStashedHandleView.setTranslationY(transY); } /** 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 1db51037de..2d8983fca7 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt @@ -18,16 +18,12 @@ package com.android.launcher3.taskbar.bubbles.animation import android.view.View import android.view.View.VISIBLE -import androidx.core.animation.AnimatorSet -import androidx.core.animation.ObjectAnimator -import androidx.core.animation.doOnEnd import androidx.dynamicanimation.animation.DynamicAnimation import androidx.dynamicanimation.animation.SpringForce import com.android.launcher3.taskbar.bubbles.BubbleBarBubble import com.android.launcher3.taskbar.bubbles.BubbleBarView import com.android.launcher3.taskbar.bubbles.BubbleStashController import com.android.launcher3.taskbar.bubbles.BubbleView -import com.android.systemui.util.doOnEnd import com.android.wm.shell.shared.animation.PhysicsAnimator /** Handles animations for bubble bar bubbles. */ @@ -43,17 +39,19 @@ constructor( /** 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_FINAL_TRANSLATION_Y = -50f + 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_INITIAL_TRANSLATION_Y = 50f + 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 initial alpha value that the new bubble is set to before the animation starts. */ - const val BUBBLE_ANIMATION_INITIAL_ALPHA = 0.5f - /** The duration of the hide bubble animation. */ - const val HIDE_BUBBLE_ANIMATION_DURATION_MS = 250L + /** + * 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 } /** An interface for scheduling jobs. */ @@ -91,7 +89,7 @@ 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, animator) + val showAnimation = buildShowAnimation(bubbleView, b.key) val hideAnimation = buildHideAnimation(bubbleView) scheduler.post(showAnimation) scheduler.postDelayed(FLYOUT_DELAY_MS, hideAnimation) @@ -100,71 +98,139 @@ constructor( /** * Returns a lambda that starts the animation that shows the new bubble. * - * The animation is divided into 2 parts. First the stash handle starts animating up and fades - * out. When it ends the bubble starts fading in. The bubble and stashed handle are aligned to - * give the impression of the stash handle morphing into the 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. + * + * 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. + * 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 + * visible which helps avoiding further updates when we re-enter the second part. */ private fun buildShowAnimation( bubbleView: BubbleView, key: String, - bubbleAnimator: PhysicsAnimator ): () -> Unit = { + 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 - bubbleBarView.prepareForAnimatingBubbleWhileStashed(key) - bubbleAnimator.setDefaultSpringConfig(springConfig) - bubbleAnimator - .spring(DynamicAnimation.ALPHA, 1f) - .spring(DynamicAnimation.TRANSLATION_Y, BUBBLE_ANIMATION_FINAL_TRANSLATION_Y) - .spring(DynamicAnimation.SCALE_Y, 1f) // prepare the bubble for the animation bubbleView.alpha = 0f bubbleView.translationX = initialTranslationX - bubbleView.translationY = BUBBLE_ANIMATION_INITIAL_TRANSLATION_Y bubbleView.scaleY = BUBBLE_ANIMATION_INITIAL_SCALE_Y bubbleView.visibility = VISIBLE - // start the stashed handle animation. when it ends, start the bubble animation. - val stashedHandleAnimation = bubbleStashController.buildHideHandleAnimationForNewBubble() - stashedHandleAnimation.doOnEnd { - bubbleView.alpha = BUBBLE_ANIMATION_INITIAL_ALPHA - bubbleAnimator.start() - bubbleStashController.setStashAlpha(0f) + + // 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 + val animator = bubbleStashController.stashedHandlePhysicsAnimator + animator.setDefaultSpringConfig(springConfig) + animator.spring(DynamicAnimation.TRANSLATION_Y, totalTranslationY) + animator.addUpdateListener { target, values -> + val ty = values[DynamicAnimation.TRANSLATION_Y]?.value ?: return@addUpdateListener + when { + ty >= BUBBLE_ANIMATION_STASH_HANDLE_TRANSLATION_Y -> { + // we're in the first leg of the animation. only animate the handle. the bubble + // 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 / 2 + } + ty >= totalTranslationY -> { + // this is the second leg of the animation. the handle should be completely + // hidden and the bubble 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 + // 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] + val fraction = + (ty - BUBBLE_ANIMATION_STASH_HANDLE_TRANSLATION_Y) / + BUBBLE_ANIMATION_BUBBLE_TRANSLATION_Y + bubbleView.alpha = fraction + bubbleView.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 + } + } } - stashedHandleAnimation.start() + animator.start() } /** * Returns a lambda that starts the animation that hides the new bubble. * - * Similarly to the show animation, this is 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. + * 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. + * + * 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. */ private fun buildHideAnimation(bubbleView: BubbleView): () -> Unit = { - val stashAnimation = bubbleStashController.buildShowHandleAnimationForNewBubble() - val alphaAnimator = - ObjectAnimator.ofFloat(bubbleView, View.ALPHA, BUBBLE_ANIMATION_INITIAL_ALPHA) - val translationYAnimator = - ObjectAnimator.ofFloat( - bubbleView, - View.TRANSLATION_Y, - BUBBLE_ANIMATION_INITIAL_TRANSLATION_Y - ) - val scaleYAnimator = - ObjectAnimator.ofFloat(bubbleView, View.SCALE_Y, BUBBLE_ANIMATION_INITIAL_SCALE_Y) - val hideBubbleAnimation = AnimatorSet() - hideBubbleAnimation.playTogether(alphaAnimator, translationYAnimator, scaleYAnimator) - hideBubbleAnimation.duration = HIDE_BUBBLE_ANIMATION_DURATION_MS - hideBubbleAnimation.doOnEnd { - // the bubble is now hidden, start the stash handle animation and reset bubble - // properties - bubbleStashController.setStashAlpha( - BubbleStashController.NEW_BUBBLE_HIDE_HANDLE_ANIMATION_ALPHA - ) + // 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 + val animator = bubbleStashController.stashedHandlePhysicsAnimator + animator.setDefaultSpringConfig(springConfig) + animator.spring(DynamicAnimation.TRANSLATION_Y, 0f) + animator.addUpdateListener { target, 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 <= 0 -> { + // this is the second part of the animation. make the bubble 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 + } + } + else -> { + // we reached the target value. set the alpha of the handle to 1 + target.alpha = 1f + } + } + } + animator.addEndListener { _, _, _, _, _, _, _ -> bubbleView.alpha = 0f - stashAnimation.start() bubbleView.translationY = 0f bubbleView.scaleY = 1f if (bubbleStashController.isStashed) { @@ -172,7 +238,7 @@ constructor( } bubbleBarView.onAnimatingBubbleCompleted() } - hideBubbleAnimation.start() + animator.start() } } 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 b478efac93..d90e0484e5 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 @@ -16,19 +16,15 @@ package com.android.launcher3.taskbar.bubbles.animation -import android.animation.Animator -import android.animation.AnimatorListenerAdapter -import android.animation.AnimatorSet import android.content.Context import android.graphics.Color 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.animation.AnimatorTestRule -import androidx.core.animation.doOnEnd import androidx.core.graphics.drawable.toBitmap import androidx.dynamicanimation.animation.DynamicAnimation import androidx.test.core.app.ApplicationProvider @@ -42,16 +38,13 @@ import com.android.launcher3.taskbar.bubbles.BubbleBarView import com.android.launcher3.taskbar.bubbles.BubbleStashController import com.android.launcher3.taskbar.bubbles.BubbleView import com.android.wm.shell.common.bubbles.BubbleInfo +import com.android.wm.shell.shared.animation.PhysicsAnimator import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils import com.google.common.truth.Truth.assertThat -import java.util.concurrent.Semaphore -import java.util.concurrent.TimeUnit import org.junit.Before -import org.junit.ClassRule 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 @@ -61,10 +54,6 @@ class BubbleBarViewAnimatorTest { private val context = ApplicationProvider.getApplicationContext() private val animatorScheduler = TestBubbleBarViewAnimatorScheduler() - companion object { - @JvmField @ClassRule val animatorTestRule = AnimatorTestRule() - } - @Before fun setUp() { PhysicsAnimatorTestUtils.prepareForTest() @@ -99,14 +88,9 @@ class BubbleBarViewAnimatorTest { val bubbleStashController = mock() whenever(bubbleStashController.isStashed).thenReturn(true) - val semaphore = Semaphore(0) - val hideHandleAnimator = AnimatorSet() - hideHandleAnimator.duration = 0 - whenever(bubbleStashController.buildHideHandleAnimationForNewBubble()) - .thenReturn(hideHandleAnimator) - // add an end listener to the hide handle animation. we add it when the animation starts - // to ensure that it gets called after all other end listeners. - hideHandleAnimator.doOnStart { hideHandleAnimator.doOnEnd { semaphore.release() } } + val handle = View(context) + val handleAnimator = PhysicsAnimator.getInstance(handle) + whenever(bubbleStashController.stashedHandlePhysicsAnimator).thenReturn(handleAnimator) val animator = BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler) @@ -115,44 +99,26 @@ class BubbleBarViewAnimatorTest { animator.animateBubbleInForStashed(bubble) } - // wait for the stash handle animation to complete - assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue() - // stash handle animation finished. verify that the stash handle is now hidden - verify(bubbleStashController).setStashAlpha(0f) - + // let the animation start and wait for it to complete InstrumentationRegistry.getInstrumentation().waitForIdleSync() + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + assertThat(handle.alpha).isEqualTo(0) + assertThat(handle.translationY).isEqualTo(-70) assertThat(overflowView.visibility).isEqualTo(INVISIBLE) assertThat(bubbleBarView.visibility).isEqualTo(VISIBLE) assertThat(bubbleView.visibility).isEqualTo(VISIBLE) - - // wait for the show bubble animation to complete - PhysicsAnimatorTestUtils.blockUntilAnimationsEnd( - DynamicAnimation.ALPHA, - DynamicAnimation.TRANSLATION_Y, - DynamicAnimation.SCALE_Y, - ) - assertThat(bubbleView.alpha).isEqualTo(1) - assertThat(bubbleView.translationY).isEqualTo(-50) + assertThat(bubbleView.translationY).isEqualTo(-20) assertThat(bubbleView.scaleY).isEqualTo(1) - val showHandleAnimator = AnimatorSet() - showHandleAnimator.duration = 0 - whenever(bubbleStashController.buildShowHandleAnimationForNewBubble()) - .thenReturn(showHandleAnimator) - var showHandleAnimationStarted = false - showHandleAnimator.doOnStart { showHandleAnimationStarted = true } - // execute the hide bubble animation assertThat(animatorScheduler.delayedBlock).isNotNull() InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!) - // finish the hide bubble animation - InstrumentationRegistry.getInstrumentation().runOnMainSync { - animatorTestRule.advanceTimeBy(250) - } - assertThat(showHandleAnimationStarted).isTrue() + // let the animation start and wait for it to complete + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) assertThat(bubbleView.alpha).isEqualTo(1) assertThat(bubbleView.visibility).isEqualTo(VISIBLE) @@ -160,16 +126,8 @@ class BubbleBarViewAnimatorTest { assertThat(bubbleBarView.alpha).isEqualTo(0) assertThat(overflowView.alpha).isEqualTo(1) assertThat(overflowView.visibility).isEqualTo(VISIBLE) - } - - private fun AnimatorSet.doOnStart(onStart: () -> Unit) { - addListener( - object : AnimatorListenerAdapter() { - override fun onAnimationStart(animator: Animator) { - onStart() - } - } - ) + assertThat(handle.alpha).isEqualTo(1) + assertThat(handle.translationY).isEqualTo(0) } private class TestBubbleBarViewAnimatorScheduler : BubbleBarViewAnimator.Scheduler {