Merge "Animate new bubble when the bar is expanded" into main
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index 85ea5fd..a0cad90 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -98,6 +98,8 @@
// During fade in animation we shift the bubble bar 1/60th of the screen width
private static final float FADE_IN_ANIM_POSITION_SHIFT = 1 / 60f;
+ private static final int SCALE_IN_ANIMATION_DURATION_MS = 250;
+
/**
* Custom property to set alpha value for the bar view while a bubble is being dragged.
* Skips applying alpha to the dragged bubble.
@@ -157,6 +159,10 @@
// collapsed state and 1 to the fully expanded state.
private final ValueAnimator mWidthAnimator = ValueAnimator.ofFloat(0, 1);
+ /** An animator used for scaling in a new bubble to the bubble bar while expanded. */
+ @Nullable
+ private ValueAnimator mNewBubbleScaleInAnimator = null;
+
@Nullable
private Animator mBubbleBarLocationAnimator = null;
@@ -212,7 +218,7 @@
mWidthAnimator.setDuration(WIDTH_ANIMATION_DURATION_MS);
mWidthAnimator.addUpdateListener(animation -> {
- updateChildrenRenderNodeProperties(mBubbleBarLocation);
+ updateBubblesLayoutProperties(mBubbleBarLocation);
invalidate();
});
mWidthAnimator.addListener(new Animator.AnimatorListener() {
@@ -298,7 +304,7 @@
if (!mDragging) {
// Position the views when not dragging
- updateChildrenRenderNodeProperties(mBubbleBarLocation);
+ updateBubblesLayoutProperties(mBubbleBarLocation);
}
}
@@ -444,7 +450,7 @@
mBubbleBarLocationAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- updateChildrenRenderNodeProperties(bubbleBarLocation);
+ updateBubblesLayoutProperties(bubbleBarLocation);
mBubbleBarBackground.setAnchorLeft(bubbleBarLocation.isOnLeft(isLayoutRtl()));
// Animate it in
@@ -613,6 +619,45 @@
mIsAnimatingNewBubble = false;
}
+ /** Add a new bubble to the bubble bar. */
+ public void addBubble(View bubble, FrameLayout.LayoutParams lp) {
+ if (isExpanded()) {
+ // if we're expanded scale the new bubble in
+ bubble.setScaleX(0f);
+ bubble.setScaleY(0f);
+ addView(bubble, 0, lp);
+ createNewBubbleScaleInAnimator(bubble);
+ mNewBubbleScaleInAnimator.start();
+ } else {
+ addView(bubble, 0, lp);
+ }
+ }
+
+ private void createNewBubbleScaleInAnimator(View bubble) {
+ mNewBubbleScaleInAnimator = ValueAnimator.ofFloat(0, 1);
+ mNewBubbleScaleInAnimator.setDuration(SCALE_IN_ANIMATION_DURATION_MS);
+ mNewBubbleScaleInAnimator.addUpdateListener(animation -> {
+ float animatedFraction = animation.getAnimatedFraction();
+ bubble.setScaleX(animatedFraction);
+ bubble.setScaleY(animatedFraction);
+ updateBubblesLayoutProperties(mBubbleBarLocation);
+ invalidate();
+ });
+ mNewBubbleScaleInAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ bubble.setScaleX(1);
+ bubble.setScaleY(1);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ updateWidth();
+ mNewBubbleScaleInAnimator = null;
+ }
+ });
+ }
+
// TODO: (b/280605790) animate it
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
@@ -663,7 +708,7 @@
* Updates the z order, positions, and badge visibility of the bubble views in the bar based
* on the expanded state.
*/
- private void updateChildrenRenderNodeProperties(BubbleBarLocation bubbleBarLocation) {
+ private void updateBubblesLayoutProperties(BubbleBarLocation bubbleBarLocation) {
final float widthState = (float) mWidthAnimator.getAnimatedValue();
final float currentWidth = getWidth();
final float expandedWidth = expandedWidth();
@@ -752,17 +797,63 @@
mBubbleBarBackground.setWidth(interpolatedWidth);
}
- private float getExpandedBubbleTranslationX(int bubbleIndex, int bubbleCount,
- boolean onLeft) {
+ private float getExpandedBubbleTranslationX(int bubbleIndex, int bubbleCount, boolean onLeft) {
if (bubbleIndex < 0 || bubbleIndex >= bubbleCount) {
return 0;
}
- if (onLeft) {
- // If bar is on the left, bubbles are ordered right to left
- return (bubbleCount - bubbleIndex - 1) * (mIconSize + mExpandedBarIconsSpacing);
+ final float iconAndSpacing = mIconSize + mExpandedBarIconsSpacing;
+ if (mNewBubbleScaleInAnimator != null && mNewBubbleScaleInAnimator.isRunning()) {
+ return getExpandedBubbleTranslationXDuringScaleAnimation(
+ bubbleIndex, bubbleCount, onLeft);
+ } else if (onLeft) {
+ return (bubbleCount - bubbleIndex - 1) * iconAndSpacing;
} else {
- // Bubbles ordered left to right, don't move the first bubble
- return bubbleIndex * (mIconSize + mExpandedBarIconsSpacing);
+ return bubbleIndex * iconAndSpacing;
+ }
+ }
+
+ /**
+ * Returns the translation X for the bubble at index {@code bubbleIndex} when the bubble bar is
+ * expanded <b>and</b> a new bubble is animating in.
+ *
+ * <p>This method assumes that the animation is running so callers are expected to verify that
+ * before calling it.
+ */
+ private float getExpandedBubbleTranslationXDuringScaleAnimation(
+ int bubbleIndex, int bubbleCount, boolean onLeft) {
+ // when the new bubble scale animation is running, a new bubble is animating in while the
+ // bubble bar is expanded, so we have at least 2 bubbles in the bubble bar - the expanded
+ // one, and the new one animating in.
+
+ if (mNewBubbleScaleInAnimator == null) {
+ // callers of this method are expected to verify that the animation is running, but the
+ // compiler doesn't know that.
+ return 0;
+ }
+ final float iconAndSpacing = mIconSize + mExpandedBarIconsSpacing;
+ final float newBubbleScale = mNewBubbleScaleInAnimator.getAnimatedFraction();
+ // the new bubble is scaling in from the center, so we need to adjust its translation so
+ // that the distance to the adjacent bubble scales at the same rate.
+ final float pivotAdjustment = -(1 - newBubbleScale) * mIconSize / 2f;
+
+ if (onLeft) {
+ if (bubbleIndex == 0) {
+ // this is the animating bubble. use scaled spacing between it and the bubble to
+ // its left
+ return (bubbleCount - 1) * mIconSize
+ + (bubbleCount - 2) * mExpandedBarIconsSpacing
+ + newBubbleScale * mExpandedBarIconsSpacing
+ + pivotAdjustment;
+ }
+ // when the bubble bar is on the left, only the translation of the right-most bubble
+ // is affected by the scale animation.
+ return (bubbleCount - bubbleIndex - 1) * iconAndSpacing;
+ } else if (bubbleIndex == 0) {
+ // the bubble bar is on the right, and this is the animating bubble. it only needs
+ // to be adjusted for the scaling pivot.
+ return pivotAdjustment;
+ } else {
+ return iconAndSpacing * (bubbleIndex - 1 + newBubbleScale);
}
}
@@ -804,7 +895,7 @@
addViewInLayout(child, i, child.getLayoutParams());
}
}
- updateChildrenRenderNodeProperties(mBubbleBarLocation);
+ updateBubblesLayoutProperties(mBubbleBarLocation);
updateContentDescription();
}
}
@@ -883,15 +974,9 @@
private float arrowPositionForSelectedWhenExpanded(BubbleBarLocation bubbleBarLocation) {
final int index = indexOfChild(mSelectedBubbleView);
- final int bubblePosition;
- if (bubbleBarLocation.isOnLeft(isLayoutRtl())) {
- // Bubble positions are reversed. First bubble is on the right.
- bubblePosition = getChildCount() - index - 1;
- } else {
- bubblePosition = index;
- }
- return getPaddingStart() + bubblePosition * (mIconSize + mExpandedBarIconsSpacing)
- + mIconSize / 2f;
+ final float selectedBubbleTranslationX = getExpandedBubbleTranslationX(
+ index, getChildCount(), bubbleBarLocation.isOnLeft(isLayoutRtl()));
+ return getPaddingStart() + selectedBubbleTranslationX + mIconSize / 2f;
}
private float arrowPositionForSelectedWhenCollapsed(BubbleBarLocation bubbleBarLocation) {
@@ -954,8 +1039,19 @@
final int childCount = getChildCount();
final int horizontalPadding = getPaddingStart() + getPaddingEnd();
// spaces amount is less than child count by 1, or 0 if no child views
- int spacesCount = Math.max(childCount - 1, 0);
- return childCount * mIconSize + spacesCount * mExpandedBarIconsSpacing + horizontalPadding;
+ final float totalSpace;
+ final float totalIconSize;
+ if (mNewBubbleScaleInAnimator != null && mNewBubbleScaleInAnimator.isRunning()) {
+ // when this animation is running, a new bubble is animating in while the bubble bar is
+ // expanded, so we have at least 2 bubbles in the bubble bar.
+ final float newBubbleScale = mNewBubbleScaleInAnimator.getAnimatedFraction();
+ totalSpace = (childCount - 2 + newBubbleScale) * mExpandedBarIconsSpacing;
+ totalIconSize = (childCount - 1 + newBubbleScale) * mIconSize;
+ } else {
+ totalSpace = Math.max(childCount - 1, 0) * mExpandedBarIconsSpacing;
+ totalIconSize = childCount * mIconSize;
+ }
+ return totalIconSize + totalSpace + horizontalPadding;
}
private float collapsedWidth() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index 45a9fa1..0bfd1d7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -402,8 +402,8 @@
*/
public void addBubble(BubbleBarItem b, boolean isExpanding, boolean suppressAnimation) {
if (b != null) {
- mBarView.addView(b.getView(), 0,
- new FrameLayout.LayoutParams(mIconSize, mIconSize, Gravity.LEFT));
+ mBarView.addBubble(
+ b.getView(), new FrameLayout.LayoutParams(mIconSize, mIconSize, Gravity.LEFT));
b.getView().setOnClickListener(mBubbleClickListener);
mBubbleDragController.setupBubbleView(b.getView());