Loading quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java +5 −35 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading @@ -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. Loading @@ -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; Loading Loading @@ -105,7 +97,6 @@ public class BubbleStashController { mBubbleStashedHandleAlpha = mHandleViewController.getStashedHandleAlpha().get( StashedHandleViewController.ALPHA_INDEX_STASHED); mBubbleStashedHandleTranslationY = mHandleViewController.getStashedHandleTranslationY(); mStashedHeight = mHandleViewController.getStashedHeight(); mUnstashedHeight = mHandleViewController.getUnstashedHeight(); Loading Loading @@ -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<View> getStashedHandlePhysicsAnimator() { return mHandleViewController.getPhysicsAnimator(); } } quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java +7 −18 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. Loading @@ -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(); Loading Loading @@ -129,6 +123,11 @@ public class BubbleStashedHandleViewController { updateBounds(mBarViewController.getBubbleBarLocation())); } /** Returns the [PhysicsAnimator] for the stashed handle view. */ public PhysicsAnimator<View> 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. Loading Loading @@ -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); } /** Loading quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt +120 −54 Original line number Diff line number Diff line Loading @@ -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. */ Loading @@ -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. */ Loading Loading @@ -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) Loading @@ -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<BubbleView> ): () -> 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) { Loading @@ -172,7 +238,7 @@ constructor( } bubbleBarView.onAnimatingBubbleCompleted() } hideBubbleAnimation.start() animator.start() } } Loading quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt +15 −57 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -61,10 +54,6 @@ class BubbleBarViewAnimatorTest { private val context = ApplicationProvider.getApplicationContext<Context>() private val animatorScheduler = TestBubbleBarViewAnimatorScheduler() companion object { @JvmField @ClassRule val animatorTestRule = AnimatorTestRule() } @Before fun setUp() { PhysicsAnimatorTestUtils.prepareForTest() Loading Loading @@ -99,14 +88,9 @@ class BubbleBarViewAnimatorTest { val bubbleStashController = mock<BubbleStashController>() 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) Loading @@ -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) Loading @@ -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 { Loading Loading
quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java +5 −35 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading @@ -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. Loading @@ -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; Loading Loading @@ -105,7 +97,6 @@ public class BubbleStashController { mBubbleStashedHandleAlpha = mHandleViewController.getStashedHandleAlpha().get( StashedHandleViewController.ALPHA_INDEX_STASHED); mBubbleStashedHandleTranslationY = mHandleViewController.getStashedHandleTranslationY(); mStashedHeight = mHandleViewController.getStashedHeight(); mUnstashedHeight = mHandleViewController.getUnstashedHeight(); Loading Loading @@ -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<View> getStashedHandlePhysicsAnimator() { return mHandleViewController.getPhysicsAnimator(); } }
quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java +7 −18 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. Loading @@ -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(); Loading Loading @@ -129,6 +123,11 @@ public class BubbleStashedHandleViewController { updateBounds(mBarViewController.getBubbleBarLocation())); } /** Returns the [PhysicsAnimator] for the stashed handle view. */ public PhysicsAnimator<View> 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. Loading Loading @@ -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); } /** Loading
quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt +120 −54 Original line number Diff line number Diff line Loading @@ -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. */ Loading @@ -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. */ Loading Loading @@ -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) Loading @@ -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<BubbleView> ): () -> 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) { Loading @@ -172,7 +238,7 @@ constructor( } bubbleBarView.onAnimatingBubbleCompleted() } hideBubbleAnimation.start() animator.start() } } Loading
quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt +15 −57 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -61,10 +54,6 @@ class BubbleBarViewAnimatorTest { private val context = ApplicationProvider.getApplicationContext<Context>() private val animatorScheduler = TestBubbleBarViewAnimatorScheduler() companion object { @JvmField @ClassRule val animatorTestRule = AnimatorTestRule() } @Before fun setUp() { PhysicsAnimatorTestUtils.prepareForTest() Loading Loading @@ -99,14 +88,9 @@ class BubbleBarViewAnimatorTest { val bubbleStashController = mock<BubbleStashController>() 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) Loading @@ -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) Loading @@ -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 { Loading