diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java index 3dcf2b4ebf..7d39bf8d4d 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java @@ -342,10 +342,10 @@ public class BubbleBarController extends IBubblesListener.Stub { mBubbleBarViewController.showOverflow(update.showOverflow); } - BubbleBarBubble bubbleToSelect = null; if (update.addedBubble != null) { mBubbles.put(update.addedBubble.getKey(), update.addedBubble); } + BubbleBarBubble bubbleToSelect = null; if (update.selectedBubbleKey != null) { if (mSelectedBubble == null || !update.selectedBubbleKey.equals(mSelectedBubble.getKey())) { @@ -363,7 +363,7 @@ public class BubbleBarController extends IBubblesListener.Stub { && update.removedBubbles.isEmpty() && !mBubbles.isEmpty()) { // A bubble was added from the overflow (& now it's empty / not showing) - mBubbleBarViewController.removeOverflowAndAddBubble(update.addedBubble); + mBubbleBarViewController.removeOverflowAndAddBubble(update.addedBubble, bubbleToSelect); } else if (update.addedBubble != null && update.removedBubbles.size() == 1) { // we're adding and removing a bubble at the same time. handle this as a single update. RemovedBubble removedBubble = update.removedBubbles.get(0); @@ -371,7 +371,8 @@ public class BubbleBarController extends IBubblesListener.Stub { boolean showOverflow = update.showOverflowChanged && update.showOverflow; if (bubbleToRemove != null) { mBubbleBarViewController.addBubbleAndRemoveBubble(update.addedBubble, - bubbleToRemove, isExpanding, suppressAnimation, showOverflow); + bubbleToRemove, bubbleToSelect, isExpanding, suppressAnimation, + showOverflow); } else { mBubbleBarViewController.addBubble(update.addedBubble, isExpanding, suppressAnimation, bubbleToSelect); @@ -387,7 +388,7 @@ public class BubbleBarController extends IBubblesListener.Stub { if (bubble != null && overflowNeedsToBeAdded) { // First removal, show the overflow overflowNeedsToBeAdded = false; - mBubbleBarViewController.addOverflowAndRemoveBubble(bubble); + mBubbleBarViewController.addOverflowAndRemoveBubble(bubble, bubbleToSelect); } else if (bubble != null) { mBubbleBarViewController.removeBubble(bubble); } else { diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java index aa6ad25425..2d4d279564 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java @@ -741,35 +741,32 @@ public class BubbleBarView extends FrameLayout { /** Add a new bubble and remove an old bubble from the bubble bar. */ public void addBubbleAndRemoveBubble(BubbleView addedBubble, BubbleView removedBubble, - Runnable onEndRunnable) { + @Nullable BubbleView bubbleToSelect, Runnable onEndRunnable) { FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams((int) mIconSize, (int) mIconSize, Gravity.LEFT); - boolean isOverflowSelected = - mSelectedBubbleView != null && mSelectedBubbleView.isOverflow(); - boolean removingOverflow = removedBubble.isOverflow(); - boolean addingOverflow = addedBubble.isOverflow(); - + int addedIndex = addedBubble.isOverflow() ? getChildCount() : 0; if (!isExpanded()) { removeView(removedBubble); - int index = addingOverflow ? getChildCount() : 0; - addView(addedBubble, index, lp); + addView(addedBubble, addedIndex, lp); if (onEndRunnable != null) { onEndRunnable.run(); } return; } - int index = addingOverflow ? getChildCount() : 0; addedBubble.setScaleX(0f); addedBubble.setScaleY(0f); - addView(addedBubble, index, lp); - - if (isOverflowSelected && removingOverflow) { - // The added bubble will be selected - mSelectedBubbleView = addedBubble; - } - int indexOfSelectedBubble = indexOfChild(mSelectedBubbleView); + addView(addedBubble, addedIndex, lp); + int indexOfCurrentSelectedBubble = indexOfChild(mSelectedBubbleView); int indexOfBubbleToRemove = indexOfChild(removedBubble); - + int indexOfNewlySelectedBubble = bubbleToSelect == null + ? indexOfCurrentSelectedBubble : indexOfChild(bubbleToSelect); + // Since removed bubble is kept till the end of the animation we should check if there are + // more than one bubble. In such a case the bar will remain open without the selected bubble + if (mSelectedBubbleView == removedBubble + && bubbleToSelect == null + && getBubbleChildCount() > 1) { + Log.w(TAG, "Remove the currently selected bubble without selecting a new one."); + } mBubbleAnimator = new BubbleAnimator(mIconSize, mExpandedBarIconsSpacing, getChildCount(), mBubbleBarLocation.isOnLeft(isLayoutRtl())); BubbleAnimator.Listener listener = new BubbleAnimator.Listener() { @@ -802,8 +799,8 @@ public class BubbleBarView extends FrameLayout { invalidate(); } }; - mBubbleAnimator.animateNewAndRemoveOld(indexOfSelectedBubble, indexOfBubbleToRemove, - listener); + mBubbleAnimator.animateNewAndRemoveOld(indexOfCurrentSelectedBubble, + indexOfNewlySelectedBubble, indexOfBubbleToRemove, addedIndex, listener); } @Override diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java index 84243f5ad1..0b627d2901 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java @@ -843,11 +843,12 @@ public class BubbleBarViewController { } /** Adds a new bubble and removes an old bubble at the same time. */ - public void addBubbleAndRemoveBubble(BubbleBarBubble addedBubble, - BubbleBarBubble removedBubble, boolean isExpanding, boolean suppressAnimation, - boolean addOverflowToo) { + public void addBubbleAndRemoveBubble(BubbleBarBubble addedBubble, BubbleBarBubble removedBubble, + @Nullable BubbleBarBubble bubbleToSelect, boolean isExpanding, + boolean suppressAnimation, boolean addOverflowToo) { + BubbleView bubbleToSelectView = bubbleToSelect == null ? null : bubbleToSelect.getView(); mBarView.addBubbleAndRemoveBubble(addedBubble.getView(), removedBubble.getView(), - addOverflowToo ? () -> showOverflow(true) : null); + bubbleToSelectView, addOverflowToo ? () -> showOverflow(true) : null); addedBubble.getView().setOnClickListener(mBubbleClickListener); addedBubble.getView().setController(mBubbleViewController); removedBubble.getView().setController(null); @@ -878,22 +879,26 @@ public class BubbleBarViewController { } /** Adds the overflow view to the bubble bar while animating a view away. */ - public void addOverflowAndRemoveBubble(BubbleBarBubble removedBubble) { + public void addOverflowAndRemoveBubble(BubbleBarBubble removedBubble, + @Nullable BubbleBarBubble bubbleToSelect) { if (mOverflowAdded) return; mOverflowAdded = true; + BubbleView bubbleToSelectView = bubbleToSelect == null ? null : bubbleToSelect.getView(); mBarView.addBubbleAndRemoveBubble(mOverflowBubble.getView(), removedBubble.getView(), - null /* onEndRunnable */); + bubbleToSelectView, null /* onEndRunnable */); mOverflowBubble.getView().setOnClickListener(mBubbleClickListener); mOverflowBubble.getView().setController(mBubbleViewController); removedBubble.getView().setController(null); } /** Removes the overflow view to the bubble bar while animating a view in. */ - public void removeOverflowAndAddBubble(BubbleBarBubble addedBubble) { + public void removeOverflowAndAddBubble(BubbleBarBubble addedBubble, + @Nullable BubbleBarBubble bubbleToSelect) { if (!mOverflowAdded) return; mOverflowAdded = false; + BubbleView bubbleToSelectView = bubbleToSelect == null ? null : bubbleToSelect.getView(); mBarView.addBubbleAndRemoveBubble(addedBubble.getView(), mOverflowBubble.getView(), - null /* onEndRunnable */); + bubbleToSelectView, null /* onEndRunnable */); addedBubble.getView().setOnClickListener(mBubbleClickListener); addedBubble.getView().setController(mBubbleViewController); mOverflowBubble.getView().setController(null); diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimator.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimator.kt index 944e80656e..26d6cccc7d 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimator.kt +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimator.kt @@ -18,6 +18,8 @@ package com.android.launcher3.taskbar.bubbles.animation import androidx.core.animation.Animator import androidx.core.animation.ValueAnimator +import kotlin.math.max +import kotlin.math.min /** * Animates individual bubbles within the bubble bar while the bubble bar is expanded. @@ -70,14 +72,18 @@ class BubbleAnimator( fun animateNewAndRemoveOld( selectedBubbleIndex: Int, + newlySelectedBubbleIndex: Int, removedBubbleIndex: Int, + addedBubbleIndex: Int, listener: Listener, ) { animator = createAnimator(listener) state = State.AddingAndRemoving( selectedBubbleIndex = selectedBubbleIndex, + newlySelectedBubbleIndex = newlySelectedBubbleIndex, removedBubbleIndex = removedBubbleIndex, + addedBubbleIndex = addedBubbleIndex, ) animator.start() } @@ -137,6 +143,7 @@ class BubbleAnimator( getBubbleTranslationXWhileAddingBubbleAtLimit( bubbleIndex = bubbleIndex, removedBubbleIndex = state.removedBubbleIndex, + addedBubbleIndex = state.addedBubbleIndex, addedBubbleScale = animator.animatedFraction, removedBubbleScale = 1 - animator.animatedFraction, ) @@ -187,34 +194,25 @@ class BubbleAnimator( State.Idle -> 0f is State.AddingBubble -> getArrowPositionWhenAddingBubble(state) is State.RemovingBubble -> getArrowPositionWhenRemovingBubble(state) - is State.AddingAndRemoving -> { - // we never remove the selected bubble, so the arrow stays pointing to its center - val tx = - getBubbleTranslationXWhileAddingBubbleAtLimit( - bubbleIndex = state.selectedBubbleIndex, - removedBubbleIndex = state.removedBubbleIndex, - addedBubbleScale = animator.animatedFraction, - removedBubbleScale = 1 - animator.animatedFraction, - ) - tx + iconSize / 2f - } + is State.AddingAndRemoving -> getArrowPositionWhenAddingAndRemovingBubble(state) } } private fun getArrowPositionWhenAddingBubble(state: State.AddingBubble): Float { val scale = animator.animatedFraction - var tx = getBubbleTranslationXWhileScalingBubble( - bubbleIndex = state.selectedBubbleIndex, - scalingBubbleIndex = 0, - bubbleScale = scale - ) + iconSize / 2f + var tx = + getBubbleTranslationXWhileScalingBubble( + bubbleIndex = state.selectedBubbleIndex, + scalingBubbleIndex = 0, + bubbleScale = scale, + ) + iconSize / 2f if (state.newlySelectedBubbleIndex != null) { val selectedBubbleScale = if (state.newlySelectedBubbleIndex == 0) scale else 1f val finalTx = getBubbleTranslationXWhileScalingBubble( bubbleIndex = state.newlySelectedBubbleIndex, scalingBubbleIndex = 0, - bubbleScale = 1f, + bubbleScale = scale, ) + iconSize * selectedBubbleScale / 2f tx += (finalTx - tx) * animator.animatedFraction } @@ -266,6 +264,46 @@ class BubbleAnimator( } } + private fun getArrowPositionWhenAddingAndRemovingBubble(state: State.AddingAndRemoving): Float { + // The bubble bar keeps constant width while adding and removing bubble. So we just need to + // find selected bubble arrow position on the animation start and newly selected bubble + // arrow position on the animation end interpolating the arrow between these positions + // during the animation. + // The indexes in the state are provided for the bubble bar containing all bubbles. So for + // certain circumstances indexes should be adjusted. + // When animation is started added bubble has zero scale as well as removed bubble when the + // animation is ended, so for both cases we should compute translation as it is one less + // bubble. + val bubbleCountOnEnd = bubbleCount - 1 + var selectedIndex = state.selectedBubbleIndex + // We only need to adjust the selected index if added bubble was added before the selected. + if (selectedIndex > state.addedBubbleIndex) { + // If the selectedIndex is higher index than the added bubble index, we need to reduce + // selectedIndex by one because the added bubble has zero scale when animation is + // started. + selectedIndex-- + } + var newlySelectedIndex = state.newlySelectedBubbleIndex + // We only need to adjust newlySelectedIndex if removed bubble was removed before the newly + // selected bubble. + if (newlySelectedIndex > state.removedBubbleIndex) { + // If the newlySelectedIndex is higher index than the removed bubble index, we need to + // reduce newlySelectedIndex by one because the removed bubble has zero scale when + // animation is ended. + newlySelectedIndex-- + } + val iconAndSpacing: Float = iconSize + expandedBarIconSpacing + val startTx = getBubblesToTheLeft(selectedIndex, bubbleCountOnEnd) * iconAndSpacing + val endTx = getBubblesToTheLeft(newlySelectedIndex, bubbleCountOnEnd) * iconAndSpacing + val tx = startTx + (endTx - startTx) * animator.animatedFraction + return tx + iconSize / 2f + } + + private fun getBubblesToTheLeft(bubbleIndex: Int, bubbleCount: Int = this.bubbleCount): Int = + // when bar is on left the index - 0 corresponds to the right - most bubble and when the + // bubble bar is on the right - 0 corresponds to the left - most bubble. + if (onLeft) bubbleCount - bubbleIndex - 1 else bubbleIndex + /** * Returns the translation X for the bubble at index {@code bubbleIndex} when the bubble bar is * expanded and a bubble is animating in or out. @@ -290,6 +328,7 @@ class BubbleAnimator( // the bar is on the left and the current bubble is to the right of the scaling // bubble so account for its scale (bubbleCount - bubbleIndex - 2 + bubbleScale) * iconAndSpacing + bubbleIndex == scalingBubbleIndex -> { // the bar is on the left and this is the scaling bubble val totalIconSize = (bubbleCount - bubbleIndex - 1) * iconSize @@ -299,6 +338,7 @@ class BubbleAnimator( val scaledSpace = bubbleScale * expandedBarIconSpacing totalIconSize + totalSpacing + scaledSpace + pivotAdjustment } + else -> // the bar is on the left and the scaling bubble is on the right. the current // bubble is unaffected by the scaling bubble @@ -310,10 +350,12 @@ class BubbleAnimator( // the bar is on the right and the scaling bubble is on the right. the current // bubble is unaffected by the scaling bubble iconAndSpacing * bubbleIndex + bubbleIndex == scalingBubbleIndex -> // the bar is on the right, and this is the animating bubble. it only needs to // be adjusted for the scaling pivot. iconAndSpacing * bubbleIndex + pivotAdjustment + else -> // the bar is on the right and the scaling bubble is on the left so account for // its scale @@ -325,61 +367,67 @@ class BubbleAnimator( private fun getBubbleTranslationXWhileAddingBubbleAtLimit( bubbleIndex: Int, removedBubbleIndex: Int, + addedBubbleIndex: Int, addedBubbleScale: Float, removedBubbleScale: Float, ): Float { val iconAndSpacing = iconSize + expandedBarIconSpacing // the bubbles are scaling from the center, so we need to adjust their translation so // that the distance to the adjacent bubble scales at the same rate. - val addedBubblePivotAdjustment = -(1 - addedBubbleScale) * iconSize / 2f - val removedBubblePivotAdjustment = -(1 - removedBubbleScale) * iconSize / 2f + val addedBubblePivotAdjustment = (addedBubbleScale - 1) * iconSize / 2f + val removedBubblePivotAdjustment = (removedBubbleScale - 1) * iconSize / 2f - return if (onLeft) { - // this is how many bubbles there are to the left of the current bubble. - // when the bubble bar is on the right the added bubble is the right-most bubble so it - // doesn't affect the translation of any other bubble. - // when the removed bubble is to the left of the current bubble, we need to subtract it - // from bubblesToLeft and use removedBubbleScale instead when calculating the - // translation. - val bubblesToLeft = bubbleCount - bubbleIndex - 1 - when { - bubbleIndex == 0 -> - // this is the added bubble and it's the right-most bubble. account for all the - // other bubbles -- including the removed bubble -- and adjust for the added - // bubble pivot. - (bubblesToLeft - 1 + removedBubbleScale) * iconAndSpacing + - addedBubblePivotAdjustment - bubbleIndex < removedBubbleIndex -> + val minAddedRemovedIndex = min(addedBubbleIndex, removedBubbleIndex) + val maxAddedRemovedIndex = max(addedBubbleIndex, removedBubbleIndex) + val isBetweenAddedAndRemoved = + bubbleIndex in (minAddedRemovedIndex + 1).. { + if (isRemovedBubbleToLeftOfAddedBubble) { // the removed bubble is to the left so account for it (bubblesToLeft - 1 + removedBubbleScale) * iconAndSpacing - bubbleIndex == removedBubbleIndex -> { - // this is the removed bubble. all the bubbles to the left are at full scale - // but we need to scale the spacing between the removed bubble and the bubble to - // its left because the removed bubble disappears towards the left side + } else { + // the added bubble is to the left so account for it + (bubblesToLeft - 1 + addedBubbleScale) * iconAndSpacing + } + } + + bubbleIndex == addedBubbleIndex -> { + if (isRemovedBubbleToLeftOfAddedBubble) { + // the removed bubble is to the left so account for it + (bubblesToLeft - 1 + removedBubbleScale) * iconAndSpacing + } else { + // it's the left-most scaling bubble, all bubbles on the left are at full scale + bubblesToLeft * iconAndSpacing + } + addedBubblePivotAdjustment + } + + bubbleIndex == removedBubbleIndex -> { + if (isRemovedBubbleToLeftOfAddedBubble) { + // All the bubbles to the left are at full scale, but we need to scale the + // spacing between the removed bubble and the bubble next to it val totalIconSize = bubblesToLeft * iconSize val totalSpacing = (bubblesToLeft - 1 + removedBubbleScale) * expandedBarIconSpacing - totalIconSize + totalSpacing + removedBubblePivotAdjustment - } - else -> - // both added and removed bubbles are to the right so they don't affect the tx - bubblesToLeft * iconAndSpacing + totalIconSize + totalSpacing + } else { + // The added bubble is to the left, so account for it + (bubblesToLeft - 1 + addedBubbleScale) * iconAndSpacing + } + removedBubblePivotAdjustment } - } else { - when { - bubbleIndex == 0 -> addedBubblePivotAdjustment // we always add bubbles at index 0 - bubbleIndex < removedBubbleIndex -> - // the bar is on the right and the removed bubble is on the right. the current - // bubble is unaffected by the removed bubble. only need to factor in the added - // bubble's scale. - iconAndSpacing * (bubbleIndex - 1 + addedBubbleScale) - bubbleIndex == removedBubbleIndex -> - // the bar is on the right, and this is the animating bubble. - iconAndSpacing * (bubbleIndex - 1 + addedBubbleScale) + - removedBubblePivotAdjustment - else -> - // both the added and the removed bubbles are to the left of the current bubble - iconAndSpacing * (bubbleIndex - 2 + addedBubbleScale + removedBubbleScale) + + else -> { + // if bubble index is on the right side of the animated bubbles, we need to deduct + // one, since both the added and the removed bubbles takes a single place + val onTheRightOfAnimatedBubbles = + if (onLeft) { + bubbleIndex < minAddedRemovedIndex + } else { + bubbleIndex > maxAddedRemovedIndex + } + (bubblesToLeft - if (onTheRightOfAnimatedBubbles) 1 else 0) * iconAndSpacing } } } @@ -413,10 +461,17 @@ class BubbleAnimator( val removingLastRemainingBubble: Boolean, ) : State - // TODO add index where bubble is being added, and index for newly selected bubble /** A new bubble is being added and an old bubble is being removed from the bubble bar. */ - data class AddingAndRemoving(val selectedBubbleIndex: Int, val removedBubbleIndex: Int) : - State + data class AddingAndRemoving( + /** The index of the selected bubble. */ + val selectedBubbleIndex: Int, + /** The index of the newly selected bubble. */ + val newlySelectedBubbleIndex: Int, + /** The index of the bubble being removed. */ + val removedBubbleIndex: Int, + /** The index of the added bubble. */ + val addedBubbleIndex: Int, + ) : State } /** Callbacks for the animation. */ diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimatorTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimatorTest.kt index 3ca36ec8d5..da362bdfb3 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimatorTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimatorTest.kt @@ -94,7 +94,9 @@ class BubbleAnimatorTest { InstrumentationRegistry.getInstrumentation().runOnMainSync { bubbleAnimator.animateNewAndRemoveOld( selectedBubbleIndex = 3, - removedBubbleIndex = 2, + newlySelectedBubbleIndex = 2, + removedBubbleIndex = 1, + addedBubbleIndex = 3, listener, ) }