Loading quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java +12 −0 Original line number Diff line number Diff line Loading @@ -96,6 +96,8 @@ public class BubbleBarView extends FrameLayout { private final BubbleBarBackground mBubbleBarBackground; private boolean mIsAnimatingNewBubble = false; /** * The current bounds of all the bubble bar. Note that these bounds may not account for * translation. The bounds should be retrieved using {@link #getBubbleBarBounds()} which Loading Loading @@ -457,6 +459,7 @@ public class BubbleBarView extends FrameLayout { /** Prepares for animating a bubble while being stashed. */ public void prepareForAnimatingBubbleWhileStashed(String bubbleKey) { 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. Loading @@ -477,6 +480,9 @@ public class BubbleBarView extends FrameLayout { /** Resets the state after the bubble animation completed. */ 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); Loading Loading @@ -521,6 +527,12 @@ 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(); Loading quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java +42 −0 Original line number Diff line number Diff line Loading @@ -51,6 +51,15 @@ 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 @@ -64,6 +73,7 @@ public class BubbleStashController { private AnimatedFloat mIconScaleForStash; private AnimatedFloat mIconTranslationYForStash; private MultiPropertyFactory.MultiProperty mBubbleStashedHandleAlpha; private AnimatedFloat mBubbleStashedHandleTranslationY; private boolean mRequestedStashState; private boolean mRequestedExpandedState; Loading Loading @@ -95,6 +105,7 @@ public class BubbleStashController { mBubbleStashedHandleAlpha = mHandleViewController.getStashedHandleAlpha().get( StashedHandleViewController.ALPHA_INDEX_STASHED); mBubbleStashedHandleTranslationY = mHandleViewController.getStashedHandleTranslationY(); mStashedHeight = mHandleViewController.getStashedHeight(); mUnstashedHeight = mHandleViewController.getUnstashedHeight(); Loading Loading @@ -362,4 +373,35 @@ public class BubbleStashController { public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation) { mHandleViewController.setBubbleBarLocation(bubbleBarLocation); } /** Returns the x position of the center of the stashed handle. */ public float getStashedHandleCenterX() { 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; } } quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java +27 −5 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ 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 @@ -47,7 +48,7 @@ public class BubbleStashedHandleViewController { private final TaskbarActivityContext mActivity; private final StashedHandleView mStashedHandleView; private final MultiValueAlpha mTaskbarStashedHandleAlpha; private final MultiValueAlpha mStashedHandleAlpha; // Initialized in init. private BubbleBarViewController mBarViewController; Loading @@ -58,6 +59,12 @@ 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 @@ -75,7 +82,7 @@ public class BubbleStashedHandleViewController { StashedHandleView stashedHandleView) { mActivity = activity; mStashedHandleView = stashedHandleView; mTaskbarStashedHandleAlpha = new MultiValueAlpha(mStashedHandleView, 1); mStashedHandleAlpha = new MultiValueAlpha(mStashedHandleView, 1); } public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) { Loading @@ -93,7 +100,7 @@ public class BubbleStashedHandleViewController { R.dimen.transient_taskbar_bottom_margin); mStashedHandleView.getLayoutParams().height = mBarSize + bottomMargin; mTaskbarStashedHandleAlpha.get(0).setValue(0); mStashedHandleAlpha.get(0).setValue(0); mStashedTaskbarHeight = resources.getDimensionPixelSize( R.dimen.bubblebar_stashed_size); Loading Loading @@ -231,18 +238,33 @@ 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) { mStashedHandleView.setTranslationY(transY); mSwipeUpTranslationY = transY; updateTranslationY(); } private void updateTranslationY() { mStashedHandleView.setTranslationY(mStashedHandleTranslationY.value + mSwipeUpTranslationY); } /** * Used by {@link BubbleStashController} to animate the handle when stashing or un stashing. */ public MultiPropertyFactory<View> getStashedHandleAlpha() { return mTaskbarStashedHandleAlpha; return mStashedHandleAlpha; } /** Returns the x position of the center of the stashed handle. */ public float getStashedHandleCenterX() { return mStashedHandleBounds.exactCenterX(); } /** Loading quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt +89 −22 Original line number Diff line number Diff line Loading @@ -18,12 +18,16 @@ 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 @@ -39,7 +43,17 @@ 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_TRANSLATION_Y = -50f const val BUBBLE_ANIMATION_FINAL_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 /** 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 } /** An interface for scheduling jobs. */ Loading Loading @@ -78,41 +92,94 @@ constructor( // 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 hideAnimation = buildHideAnimation(animator) val hideAnimation = buildHideAnimation(bubbleView) scheduler.post(showAnimation) scheduler.postDelayed(FLYOUT_DELAY_MS, hideAnimation) } /** Returns a lambda that starts the animation that shows the new bubble. */ /** * 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. */ private fun buildShowAnimation( bubbleView: BubbleView, key: String, animator: PhysicsAnimator<BubbleView> bubbleAnimator: PhysicsAnimator<BubbleView> ): () -> Unit = { // 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) animator.setDefaultSpringConfig(springConfig) animator bubbleAnimator.setDefaultSpringConfig(springConfig) bubbleAnimator .spring(DynamicAnimation.ALPHA, 1f) .spring(DynamicAnimation.TRANSLATION_Y, BUBBLE_ANIMATION_TRANSLATION_Y) .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 animator.start() // 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) } stashedHandleAnimation.start() } /** Returns a lambda that starts the animation that hides the new bubble. */ private fun buildHideAnimation(animator: PhysicsAnimator<BubbleView>): () -> Unit = { animator.setDefaultSpringConfig(springConfig) animator .spring(DynamicAnimation.ALPHA, 0f) .spring(DynamicAnimation.TRANSLATION_Y, 0f) .addEndListener { _, _, _, canceled, _, _, allRelevantPropertyAnimsEnded -> if (!canceled && allRelevantPropertyAnimsEnded) { /** * 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. */ 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 ) bubbleView.alpha = 0f stashAnimation.start() bubbleView.translationY = 0f bubbleView.scaleY = 1f if (bubbleStashController.isStashed) { bubbleBarView.alpha = 0f } bubbleBarView.onAnimatingBubbleCompleted() } hideBubbleAnimation.start() } animator.start() } /** 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 } quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt +54 −7 Original line number Diff line number Diff line Loading @@ -16,6 +16,9 @@ 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 Loading @@ -24,6 +27,8 @@ import android.view.LayoutInflater 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 @@ -39,10 +44,14 @@ import com.android.launcher3.taskbar.bubbles.BubbleView import com.android.wm.shell.common.bubbles.BubbleInfo 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 @@ -52,6 +61,10 @@ 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 @@ -86,6 +99,15 @@ 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 animator = BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler) Loading @@ -93,29 +115,44 @@ 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) InstrumentationRegistry.getInstrumentation().waitForIdleSync() 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.TRANSLATION_Y, DynamicAnimation.SCALE_Y, ) assertThat(bubbleView.alpha).isEqualTo(1) assertThat(bubbleView.translationY).isEqualTo(-50) 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) } InstrumentationRegistry.getInstrumentation().waitForIdleSync() PhysicsAnimatorTestUtils.blockUntilAnimationsEnd( DynamicAnimation.ALPHA, DynamicAnimation.TRANSLATION_Y ) assertThat(showHandleAnimationStarted).isTrue() assertThat(bubbleView.alpha).isEqualTo(1) assertThat(bubbleView.visibility).isEqualTo(VISIBLE) Loading @@ -125,6 +162,16 @@ class BubbleBarViewAnimatorTest { assertThat(overflowView.visibility).isEqualTo(VISIBLE) } private fun AnimatorSet.doOnStart(onStart: () -> Unit) { addListener( object : AnimatorListenerAdapter() { override fun onAnimationStart(animator: Animator) { onStart() } } ) } private class TestBubbleBarViewAnimatorScheduler : BubbleBarViewAnimator.Scheduler { var delayedBlock: (() -> Unit)? = null Loading Loading
quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java +12 −0 Original line number Diff line number Diff line Loading @@ -96,6 +96,8 @@ public class BubbleBarView extends FrameLayout { private final BubbleBarBackground mBubbleBarBackground; private boolean mIsAnimatingNewBubble = false; /** * The current bounds of all the bubble bar. Note that these bounds may not account for * translation. The bounds should be retrieved using {@link #getBubbleBarBounds()} which Loading Loading @@ -457,6 +459,7 @@ public class BubbleBarView extends FrameLayout { /** Prepares for animating a bubble while being stashed. */ public void prepareForAnimatingBubbleWhileStashed(String bubbleKey) { 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. Loading @@ -477,6 +480,9 @@ public class BubbleBarView extends FrameLayout { /** Resets the state after the bubble animation completed. */ 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); Loading Loading @@ -521,6 +527,12 @@ 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(); Loading
quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java +42 −0 Original line number Diff line number Diff line Loading @@ -51,6 +51,15 @@ 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 @@ -64,6 +73,7 @@ public class BubbleStashController { private AnimatedFloat mIconScaleForStash; private AnimatedFloat mIconTranslationYForStash; private MultiPropertyFactory.MultiProperty mBubbleStashedHandleAlpha; private AnimatedFloat mBubbleStashedHandleTranslationY; private boolean mRequestedStashState; private boolean mRequestedExpandedState; Loading Loading @@ -95,6 +105,7 @@ public class BubbleStashController { mBubbleStashedHandleAlpha = mHandleViewController.getStashedHandleAlpha().get( StashedHandleViewController.ALPHA_INDEX_STASHED); mBubbleStashedHandleTranslationY = mHandleViewController.getStashedHandleTranslationY(); mStashedHeight = mHandleViewController.getStashedHeight(); mUnstashedHeight = mHandleViewController.getUnstashedHeight(); Loading Loading @@ -362,4 +373,35 @@ public class BubbleStashController { public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation) { mHandleViewController.setBubbleBarLocation(bubbleBarLocation); } /** Returns the x position of the center of the stashed handle. */ public float getStashedHandleCenterX() { 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; } }
quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java +27 −5 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ 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 @@ -47,7 +48,7 @@ public class BubbleStashedHandleViewController { private final TaskbarActivityContext mActivity; private final StashedHandleView mStashedHandleView; private final MultiValueAlpha mTaskbarStashedHandleAlpha; private final MultiValueAlpha mStashedHandleAlpha; // Initialized in init. private BubbleBarViewController mBarViewController; Loading @@ -58,6 +59,12 @@ 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 @@ -75,7 +82,7 @@ public class BubbleStashedHandleViewController { StashedHandleView stashedHandleView) { mActivity = activity; mStashedHandleView = stashedHandleView; mTaskbarStashedHandleAlpha = new MultiValueAlpha(mStashedHandleView, 1); mStashedHandleAlpha = new MultiValueAlpha(mStashedHandleView, 1); } public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) { Loading @@ -93,7 +100,7 @@ public class BubbleStashedHandleViewController { R.dimen.transient_taskbar_bottom_margin); mStashedHandleView.getLayoutParams().height = mBarSize + bottomMargin; mTaskbarStashedHandleAlpha.get(0).setValue(0); mStashedHandleAlpha.get(0).setValue(0); mStashedTaskbarHeight = resources.getDimensionPixelSize( R.dimen.bubblebar_stashed_size); Loading Loading @@ -231,18 +238,33 @@ 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) { mStashedHandleView.setTranslationY(transY); mSwipeUpTranslationY = transY; updateTranslationY(); } private void updateTranslationY() { mStashedHandleView.setTranslationY(mStashedHandleTranslationY.value + mSwipeUpTranslationY); } /** * Used by {@link BubbleStashController} to animate the handle when stashing or un stashing. */ public MultiPropertyFactory<View> getStashedHandleAlpha() { return mTaskbarStashedHandleAlpha; return mStashedHandleAlpha; } /** Returns the x position of the center of the stashed handle. */ public float getStashedHandleCenterX() { return mStashedHandleBounds.exactCenterX(); } /** Loading
quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt +89 −22 Original line number Diff line number Diff line Loading @@ -18,12 +18,16 @@ 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 @@ -39,7 +43,17 @@ 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_TRANSLATION_Y = -50f const val BUBBLE_ANIMATION_FINAL_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 /** 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 } /** An interface for scheduling jobs. */ Loading Loading @@ -78,41 +92,94 @@ constructor( // 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 hideAnimation = buildHideAnimation(animator) val hideAnimation = buildHideAnimation(bubbleView) scheduler.post(showAnimation) scheduler.postDelayed(FLYOUT_DELAY_MS, hideAnimation) } /** Returns a lambda that starts the animation that shows the new bubble. */ /** * 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. */ private fun buildShowAnimation( bubbleView: BubbleView, key: String, animator: PhysicsAnimator<BubbleView> bubbleAnimator: PhysicsAnimator<BubbleView> ): () -> Unit = { // 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) animator.setDefaultSpringConfig(springConfig) animator bubbleAnimator.setDefaultSpringConfig(springConfig) bubbleAnimator .spring(DynamicAnimation.ALPHA, 1f) .spring(DynamicAnimation.TRANSLATION_Y, BUBBLE_ANIMATION_TRANSLATION_Y) .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 animator.start() // 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) } stashedHandleAnimation.start() } /** Returns a lambda that starts the animation that hides the new bubble. */ private fun buildHideAnimation(animator: PhysicsAnimator<BubbleView>): () -> Unit = { animator.setDefaultSpringConfig(springConfig) animator .spring(DynamicAnimation.ALPHA, 0f) .spring(DynamicAnimation.TRANSLATION_Y, 0f) .addEndListener { _, _, _, canceled, _, _, allRelevantPropertyAnimsEnded -> if (!canceled && allRelevantPropertyAnimsEnded) { /** * 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. */ 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 ) bubbleView.alpha = 0f stashAnimation.start() bubbleView.translationY = 0f bubbleView.scaleY = 1f if (bubbleStashController.isStashed) { bubbleBarView.alpha = 0f } bubbleBarView.onAnimatingBubbleCompleted() } hideBubbleAnimation.start() } animator.start() } /** 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 }
quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt +54 −7 Original line number Diff line number Diff line Loading @@ -16,6 +16,9 @@ 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 Loading @@ -24,6 +27,8 @@ import android.view.LayoutInflater 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 @@ -39,10 +44,14 @@ import com.android.launcher3.taskbar.bubbles.BubbleView import com.android.wm.shell.common.bubbles.BubbleInfo 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 @@ -52,6 +61,10 @@ 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 @@ -86,6 +99,15 @@ 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 animator = BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler) Loading @@ -93,29 +115,44 @@ 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) InstrumentationRegistry.getInstrumentation().waitForIdleSync() 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.TRANSLATION_Y, DynamicAnimation.SCALE_Y, ) assertThat(bubbleView.alpha).isEqualTo(1) assertThat(bubbleView.translationY).isEqualTo(-50) 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) } InstrumentationRegistry.getInstrumentation().waitForIdleSync() PhysicsAnimatorTestUtils.blockUntilAnimationsEnd( DynamicAnimation.ALPHA, DynamicAnimation.TRANSLATION_Y ) assertThat(showHandleAnimationStarted).isTrue() assertThat(bubbleView.alpha).isEqualTo(1) assertThat(bubbleView.visibility).isEqualTo(VISIBLE) Loading @@ -125,6 +162,16 @@ class BubbleBarViewAnimatorTest { assertThat(overflowView.visibility).isEqualTo(VISIBLE) } private fun AnimatorSet.doOnStart(onStart: () -> Unit) { addListener( object : AnimatorListenerAdapter() { override fun onAnimationStart(animator: Animator) { onStart() } } ) } private class TestBubbleBarViewAnimatorScheduler : BubbleBarViewAnimator.Scheduler { var delayedBlock: (() -> Unit)? = null Loading