Animate the bubble bar for the first bubble
This change animates the bubble bar for the first bubble. When we're in home -- the bubble bar bounces in and stays visible When we're in app -- the bubble bar bounces in, then animates to the stashed handle. When the bubble bar auto expands, we currently animate the bubble bar already expanded. In the future we'll time the expansion until after the bubble bar settles in. Demo: http://recall/-/bJtug1HhvXkkeA4MQvIaiP/hIGwtb3YKyCT9Ke9adZNgY Flag: ACONFIG com.android.wm.shell.enable_bubble_bar DEVELOPMENT Bug: 339066271 Test: atest BubbleBarViewAnimatorTest Change-Id: I80ce55676b72a50bab23623e0b5c1e602690682f
This commit is contained in:
@@ -403,9 +403,6 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
}
|
||||
if (bubbleToSelect != null) {
|
||||
setSelectedBubbleInternal(bubbleToSelect);
|
||||
if (previouslySelectedBubble == null) {
|
||||
mBubbleStashController.animateToInitialState(update.expanded);
|
||||
}
|
||||
}
|
||||
if (update.shouldShowEducation) {
|
||||
mBubbleBarViewController.prepareToShowEducation();
|
||||
|
||||
@@ -406,10 +406,21 @@ public class BubbleBarViewController {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(b instanceof BubbleBarBubble bubble)) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isInApp = mTaskbarStashController.isInApp();
|
||||
// if this is the first bubble, animate to the initial state. one bubble is the overflow
|
||||
// so check for at most 2 children.
|
||||
if (mBarView.getChildCount() <= 2) {
|
||||
mBubbleBarViewAnimator.animateToInitialState(bubble, isInApp, isExpanding);
|
||||
return;
|
||||
}
|
||||
|
||||
// only animate the new bubble if we're in an app and not auto expanding
|
||||
if (b instanceof BubbleBarBubble && isInApp && !isExpanding && !isExpanded()) {
|
||||
mBubbleBarViewAnimator.animateBubbleInForStashed((BubbleBarBubble) b);
|
||||
if (isInApp && !isExpanding && !isExpanded()) {
|
||||
mBubbleBarViewAnimator.animateBubbleInForStashed(bubble);
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "addBubble, bubble was null!");
|
||||
|
||||
@@ -123,21 +123,17 @@ public class BubbleStashController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the bubble bar and handle to their initial state, transitioning from the state where
|
||||
* both views are invisible. Called when the first bubble is added or when the device is
|
||||
* Animates the handle (or bubble bar depending on state) to be visible after the device is
|
||||
* unlocked.
|
||||
*
|
||||
* <p>Normally either the bubble bar or the handle is visible,
|
||||
* and {@link #showBubbleBar(boolean)} and {@link #stashBubbleBar()} are used to transition
|
||||
* between these two states. But the transition from the state where both the bar and handle
|
||||
* are invisible is slightly different.
|
||||
*
|
||||
* <p>The initial state will depend on the current state of the device, i.e. overview, home etc
|
||||
* and whether bubbles are requested to be expanded.
|
||||
*/
|
||||
public void animateToInitialState(boolean expanding) {
|
||||
private void animateAfterUnlock() {
|
||||
AnimatorSet animatorSet = new AnimatorSet();
|
||||
if (expanding || mBubblesShowingOnHome || mBubblesShowingOnOverview) {
|
||||
if (mBubblesShowingOnHome || mBubblesShowingOnOverview) {
|
||||
mIsStashed = false;
|
||||
animatorSet.playTogether(mIconScaleForStash.animateToValue(1),
|
||||
mIconTranslationYForStash.animateToValue(getBubbleBarTranslationY()),
|
||||
@@ -217,7 +213,7 @@ public class BubbleStashController {
|
||||
if (isSysuiLocked != mIsSysuiLocked) {
|
||||
mIsSysuiLocked = isSysuiLocked;
|
||||
if (!mIsSysuiLocked && mBarViewController.hasBubbles()) {
|
||||
animateToInitialState(false /* expanding */);
|
||||
animateAfterUnlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -453,4 +449,9 @@ public class BubbleStashController {
|
||||
mIsStashed = isStashed;
|
||||
onIsStashedChanged();
|
||||
}
|
||||
|
||||
/** Set the translation Y for the stashed handle. */
|
||||
public void setHandleTranslationY(float ty) {
|
||||
mHandleViewController.setTranslationYForSwipe(ty);
|
||||
}
|
||||
}
|
||||
|
||||
+52
-6
@@ -93,15 +93,15 @@ 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()
|
||||
val hideAnimation = buildHideAnimation()
|
||||
val showAnimation = buildHandleToBubbleBarAnimation()
|
||||
val hideAnimation = buildBubbleBarToHandleAnimation()
|
||||
animatingBubble = AnimatingBubble(bubbleView, showAnimation, hideAnimation)
|
||||
scheduler.post(showAnimation)
|
||||
scheduler.postDelayed(FLYOUT_DELAY_MS, hideAnimation)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a [Runnable] that starts the animation that shows the new or updated bubble.
|
||||
* Returns a [Runnable] that starts the animation that morphs the handle to the bubble bar.
|
||||
*
|
||||
* Visually, the animation is divided into 2 parts. The stash handle starts animating up and
|
||||
* fading out and then the bubble bar starts animating up and fading in.
|
||||
@@ -114,7 +114,7 @@ constructor(
|
||||
* 3. The third part is the overshoot of the spring animation, where we make the bubble fully
|
||||
* visible which helps avoiding further updates when we re-enter the second part.
|
||||
*/
|
||||
private fun buildShowAnimation() = Runnable {
|
||||
private fun buildHandleToBubbleBarAnimation() = Runnable {
|
||||
// prepare the bubble bar for the animation
|
||||
bubbleBarView.onAnimatingBubbleStarted()
|
||||
bubbleBarView.visibility = VISIBLE
|
||||
@@ -197,7 +197,8 @@ constructor(
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a [Runnable] that starts the animation that hides the bubble bar.
|
||||
* Returns a [Runnable] that starts the animation that hides the bubble bar and morphs it into
|
||||
* the stashed handle.
|
||||
*
|
||||
* Similarly to the show animation, this is visually divided into 2 parts. We first animate the
|
||||
* bubble bar out, and then animate the stash handle in. At the end of the animation we reset
|
||||
@@ -209,13 +210,14 @@ constructor(
|
||||
* 2. In the second part the bubble bar is fully hidden and the handle animates in.
|
||||
* 3. The third part is the overshoot. The handle is made fully visible.
|
||||
*/
|
||||
private fun buildHideAnimation() = Runnable {
|
||||
private fun buildBubbleBarToHandleAnimation() = Runnable {
|
||||
if (animatingBubble == null) return@Runnable
|
||||
val offset = bubbleStashController.diffBetweenHandleAndBarCenters
|
||||
val stashedHandleTranslationY =
|
||||
bubbleStashController.stashedHandleTranslationForNewBubbleAnimation
|
||||
// this is the total distance that both the stashed handle and the bar will be traveling
|
||||
val totalTranslationY = bubbleStashController.bubbleBarTranslationYForTaskbar + offset
|
||||
bubbleStashController.setHandleTranslationY(totalTranslationY)
|
||||
val animator = bubbleStashController.stashedHandlePhysicsAnimator
|
||||
animator.setDefaultSpringConfig(springConfig)
|
||||
animator.spring(DynamicAnimation.TRANSLATION_Y, 0f)
|
||||
@@ -259,6 +261,50 @@ constructor(
|
||||
animator.start()
|
||||
}
|
||||
|
||||
/** Animates to the initial state of the bubble bar, when there are no previous bubbles. */
|
||||
fun animateToInitialState(b: BubbleBarBubble, isInApp: Boolean, isExpanding: Boolean) {
|
||||
val bubbleView = b.view
|
||||
val animator = PhysicsAnimator.getInstance(bubbleView)
|
||||
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 if we are in an app.
|
||||
val showAnimation = buildBubbleBarBounceAnimation()
|
||||
val hideAnimation =
|
||||
if (isInApp && !isExpanding) {
|
||||
buildBubbleBarToHandleAnimation()
|
||||
} else {
|
||||
// in this case the bubble bar remains visible so not much to do. once we implement
|
||||
// the flyout we'll update this runnable to hide it.
|
||||
Runnable {
|
||||
animatingBubble = null
|
||||
bubbleStashController.showBubbleBarImmediate()
|
||||
bubbleBarView.onAnimatingBubbleCompleted()
|
||||
}
|
||||
}
|
||||
animatingBubble = AnimatingBubble(bubbleView, showAnimation, hideAnimation)
|
||||
scheduler.post(showAnimation)
|
||||
scheduler.postDelayed(FLYOUT_DELAY_MS, hideAnimation)
|
||||
}
|
||||
|
||||
private fun buildBubbleBarBounceAnimation() = Runnable {
|
||||
// prepare the bubble bar for the animation
|
||||
bubbleBarView.onAnimatingBubbleStarted()
|
||||
bubbleBarView.translationY = bubbleBarView.height.toFloat()
|
||||
bubbleBarView.visibility = VISIBLE
|
||||
bubbleBarView.alpha = 1f
|
||||
bubbleBarView.scaleX = 1f
|
||||
bubbleBarView.scaleY = 1f
|
||||
|
||||
val animator = PhysicsAnimator.getInstance(bubbleBarView)
|
||||
animator.setDefaultSpringConfig(springConfig)
|
||||
animator.spring(DynamicAnimation.TRANSLATION_Y, bubbleStashController.bubbleBarTranslationY)
|
||||
animator.addEndListener { _, _, _, _, _, _, _ ->
|
||||
// the bubble bar is now fully settled in. update taskbar touch region so it's touchable
|
||||
bubbleStashController.updateTaskbarTouchRegion()
|
||||
}
|
||||
animator.start()
|
||||
}
|
||||
|
||||
/** Handles clicking on the animating bubble while the animation is still playing. */
|
||||
fun onBubbleClickedWhileAnimating() {
|
||||
val hideAnimation = animatingBubble?.hideAnimation ?: return
|
||||
|
||||
+115
@@ -264,6 +264,120 @@ class BubbleBarViewAnimatorTest {
|
||||
assertThat(animatorScheduler.delayedBlock).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun animateToInitialState_inApp() {
|
||||
setUpBubbleBar()
|
||||
setUpBubbleStashController()
|
||||
whenever(bubbleStashController.bubbleBarTranslationY)
|
||||
.thenReturn(BAR_TRANSLATION_Y_FOR_TASKBAR)
|
||||
|
||||
val handle = View(context)
|
||||
val handleAnimator = PhysicsAnimator.getInstance(handle)
|
||||
whenever(bubbleStashController.stashedHandlePhysicsAnimator).thenReturn(handleAnimator)
|
||||
|
||||
val barAnimator = PhysicsAnimator.getInstance(bubbleBarView)
|
||||
|
||||
val animator =
|
||||
BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
|
||||
|
||||
InstrumentationRegistry.getInstrumentation().runOnMainSync {
|
||||
animator.animateToInitialState(bubble, isInApp = true, isExpanding = false)
|
||||
}
|
||||
|
||||
InstrumentationRegistry.getInstrumentation().runOnMainSync {}
|
||||
PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
|
||||
|
||||
assertThat(barAnimator.isRunning()).isFalse()
|
||||
assertThat(bubbleBarView.isAnimatingNewBubble).isTrue()
|
||||
assertThat(bubbleBarView.alpha).isEqualTo(1)
|
||||
assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_TASKBAR)
|
||||
|
||||
assertThat(animatorScheduler.delayedBlock).isNotNull()
|
||||
InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!)
|
||||
|
||||
InstrumentationRegistry.getInstrumentation().runOnMainSync {}
|
||||
PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
|
||||
|
||||
assertThat(bubbleBarView.isAnimatingNewBubble).isFalse()
|
||||
assertThat(bubbleBarView.alpha).isEqualTo(0)
|
||||
assertThat(handle.translationY).isEqualTo(0)
|
||||
assertThat(handle.alpha).isEqualTo(1)
|
||||
|
||||
verify(bubbleStashController).stashBubbleBarImmediate()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun animateToInitialState_inApp_autoExpanding() {
|
||||
setUpBubbleBar()
|
||||
setUpBubbleStashController()
|
||||
whenever(bubbleStashController.bubbleBarTranslationY)
|
||||
.thenReturn(BAR_TRANSLATION_Y_FOR_TASKBAR)
|
||||
|
||||
val handle = View(context)
|
||||
val handleAnimator = PhysicsAnimator.getInstance(handle)
|
||||
whenever(bubbleStashController.stashedHandlePhysicsAnimator).thenReturn(handleAnimator)
|
||||
|
||||
val barAnimator = PhysicsAnimator.getInstance(bubbleBarView)
|
||||
|
||||
val animator =
|
||||
BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
|
||||
|
||||
InstrumentationRegistry.getInstrumentation().runOnMainSync {
|
||||
animator.animateToInitialState(bubble, isInApp = true, isExpanding = true)
|
||||
}
|
||||
|
||||
InstrumentationRegistry.getInstrumentation().runOnMainSync {}
|
||||
PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
|
||||
|
||||
assertThat(barAnimator.isRunning()).isFalse()
|
||||
assertThat(bubbleBarView.isAnimatingNewBubble).isTrue()
|
||||
assertThat(bubbleBarView.alpha).isEqualTo(1)
|
||||
assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_TASKBAR)
|
||||
|
||||
assertThat(animatorScheduler.delayedBlock).isNotNull()
|
||||
InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!)
|
||||
|
||||
assertThat(bubbleBarView.isAnimatingNewBubble).isFalse()
|
||||
assertThat(bubbleBarView.alpha).isEqualTo(1)
|
||||
assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_TASKBAR)
|
||||
|
||||
verify(bubbleStashController).showBubbleBarImmediate()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun animateToInitialState_inHome() {
|
||||
setUpBubbleBar()
|
||||
setUpBubbleStashController()
|
||||
whenever(bubbleStashController.bubbleBarTranslationY)
|
||||
.thenReturn(BAR_TRANSLATION_Y_FOR_HOTSEAT)
|
||||
|
||||
val barAnimator = PhysicsAnimator.getInstance(bubbleBarView)
|
||||
|
||||
val animator =
|
||||
BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
|
||||
|
||||
InstrumentationRegistry.getInstrumentation().runOnMainSync {
|
||||
animator.animateToInitialState(bubble, isInApp = false, isExpanding = false)
|
||||
}
|
||||
|
||||
InstrumentationRegistry.getInstrumentation().runOnMainSync {}
|
||||
PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
|
||||
|
||||
assertThat(barAnimator.isRunning()).isFalse()
|
||||
assertThat(bubbleBarView.isAnimatingNewBubble).isTrue()
|
||||
assertThat(bubbleBarView.alpha).isEqualTo(1)
|
||||
assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_HOTSEAT)
|
||||
|
||||
assertThat(animatorScheduler.delayedBlock).isNotNull()
|
||||
InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!)
|
||||
|
||||
assertThat(bubbleBarView.isAnimatingNewBubble).isFalse()
|
||||
assertThat(bubbleBarView.alpha).isEqualTo(1)
|
||||
assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_HOTSEAT)
|
||||
|
||||
verify(bubbleStashController).showBubbleBarImmediate()
|
||||
}
|
||||
|
||||
private fun setUpBubbleBar() {
|
||||
bubbleBarView = BubbleBarView(context)
|
||||
InstrumentationRegistry.getInstrumentation().runOnMainSync {
|
||||
@@ -322,3 +436,4 @@ class BubbleBarViewAnimatorTest {
|
||||
private const val DIFF_BETWEEN_HANDLE_AND_BAR_CENTERS = -20f
|
||||
private const val HANDLE_TRANSLATION = -30f
|
||||
private const val BAR_TRANSLATION_Y_FOR_TASKBAR = -50f
|
||||
private const val BAR_TRANSLATION_Y_FOR_HOTSEAT = -40f
|
||||
|
||||
Reference in New Issue
Block a user