From 233218f9806f2e71844f20b383fcc1d71dd24f13 Mon Sep 17 00:00:00 2001 From: Lyn Han Date: Wed, 21 Oct 2020 20:03:16 -0500 Subject: [PATCH] Animate bubble swap for update while collapsed On reorder: 1) Animate bubbles to new location 2) Remove then add views at updated index This animation has two parts running in parallel: 1) The updated bubble - animates up, slightly above final position - scales down for emphasis - shows badge and dot (future cl to animate in badge as separate view) 2) The rest of stack bubbles - animate down, slight below final positions - scale down for emphasis - hide badge and dot Also: once the stack changes sides, update BadgedImageView#mOnLeft - this fixes a bug where the badge briefly appears on the previous side before quickly correcting to the updated side. Bug: 170267512 Test: update bubble at bottom of collapsed stack => updated bubble swoops in above, becomes top bubble => previously top bubble swoops out below, settles into new index with rest of stack Test: repeat above on right side and after expanding/collapsing stack => no regressions Change-Id: I146528efb1013da694698deca0059a51da70efd9 --- packages/SystemUI/res/values/dimens.xml | 2 + .../systemui/bubbles/BadgedImageView.java | 12 ++++ .../systemui/bubbles/BubbleStackView.java | 33 +++++++---- .../animation/StackAnimationController.java | 59 +++++++++++++++++++ 4 files changed, 93 insertions(+), 13 deletions(-) diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 4b1ed0a25c907..1ab776b2a0a81 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1218,6 +1218,8 @@ 4dp 10dp + + 15dp 9dp diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java index 9f7358bf94ff7..8bcffc8ece1aa 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java @@ -113,6 +113,18 @@ public class BadgedImageView extends ImageView { setClickable(true); } + public void showDotAndBadge(boolean onLeft) { + removeDotSuppressionFlag(BadgedImageView.SuppressionFlag.BEHIND_STACK); + animateDotBadgePositions(onLeft); + + } + + public void hideDotAndBadge(boolean onLeft) { + addDotSuppressionFlag(BadgedImageView.SuppressionFlag.BEHIND_STACK); + mOnLeft = onLeft; + hideBadge(); + } + /** * Updates the view with provided info. */ diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 0714c5eb4fa4a..1201d42b1fc5a 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -90,6 +90,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.function.Consumer; +import java.util.stream.Collectors; /** * Renders bubbles in a stack and handles animating expanded and collapsed states. @@ -1479,12 +1480,24 @@ public class BubbleStackView extends FrameLayout logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__UPDATED); } + /** + * Update bubble order and pointer position. + */ public void updateBubbleOrder(List bubbles) { - for (int i = 0; i < bubbles.size(); i++) { - Bubble bubble = bubbles.get(i); - mBubbleContainer.reorderView(bubble.getIconView(), i); + final Runnable reorder = () -> { + for (int i = 0; i < bubbles.size(); i++) { + Bubble bubble = bubbles.get(i); + mBubbleContainer.reorderView(bubble.getIconView(), i); + } + }; + if (mIsExpanded) { + reorder.run(); + updateBubbleIcons(); + } else { + List bubbleViews = bubbles.stream() + .map(b -> b.getIconView()).collect(Collectors.toList()); + mStackAnimationController.animateReorder(bubbleViews, reorder); } - updateBubbleIcons(); updatePointerPosition(); } @@ -2595,17 +2608,11 @@ public class BubbleStackView extends FrameLayout bv.setZ((mMaxBubbles * mBubbleElevation) - i); if (mIsExpanded) { - bv.removeDotSuppressionFlag( - BadgedImageView.SuppressionFlag.BEHIND_STACK); - bv.animateDotBadgePositions(false /* onLeft */); + bv.showDotAndBadge(false /* onLeft */); } else if (i == 0) { - bv.removeDotSuppressionFlag( - BadgedImageView.SuppressionFlag.BEHIND_STACK); - bv.animateDotBadgePositions(!mStackOnLeftOrWillBe); + bv.showDotAndBadge(!mStackOnLeftOrWillBe); } else { - bv.addDotSuppressionFlag( - BadgedImageView.SuppressionFlag.BEHIND_STACK); - bv.hideBadge(); + bv.hideDotAndBadge(!mStackOnLeftOrWillBe); } } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java index 31e1ca839e5d9..c410b8267dd40 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java @@ -34,6 +34,7 @@ import androidx.dynamicanimation.animation.SpringAnimation; import androidx.dynamicanimation.animation.SpringForce; import com.android.systemui.R; +import com.android.systemui.bubbles.BadgedImageView; import com.android.systemui.bubbles.BubblePositioner; import com.android.systemui.bubbles.BubbleStackView; import com.android.wm.shell.animation.PhysicsAnimator; @@ -45,6 +46,7 @@ import com.google.android.collect.Sets; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.HashMap; +import java.util.List; import java.util.Set; import java.util.function.IntSupplier; @@ -63,6 +65,10 @@ public class StackAnimationController extends private static final float ANIMATE_IN_STIFFNESS = 1000f; private static final int ANIMATE_IN_START_DELAY = 25; + /** Values to use for animating updated bubble to top of stack. */ + private static final float BUBBLE_SWAP_SCALE = 0.8f; + private static final long BUBBLE_SWAP_DURATION = 300L; + /** * Values to use for the default {@link SpringForce} provided to the physics animation layout. */ @@ -180,6 +186,12 @@ public class StackAnimationController extends /** Horizontal offset of bubbles in the stack. */ private float mStackOffset; + /** Offset between stack y and animation y for bubble swap. */ + private float mSwapAnimationOffset; + /** Max number of bubbles to show in the expanded bubble row. */ + private int mMaxBubbles; + /** Default bubble elevation. */ + private int mElevation; /** Diameter of the bubble icon. */ private int mBubbleBitmapSize; /** Width of the bubble (icon and padding). */ @@ -770,6 +782,50 @@ public class StackAnimationController extends } } + public void animateReorder(List bubbleViews, Runnable after) { + for (int newIndex = 0; newIndex < bubbleViews.size(); newIndex++) { + View view = bubbleViews.get(newIndex); + final int oldIndex= mLayout.indexOfChild(view); + animateSwap(view, oldIndex, newIndex, after); + } + } + + private void animateSwap(View view, int oldIndex, int newIndex, Runnable finishReorder) { + final float newY = getStackPosition().y + newIndex * mSwapAnimationOffset; + final float swapY = newIndex == 0 + ? newY - mSwapAnimationOffset // Above top of stack + : newY + mSwapAnimationOffset; // Below where bubble will be + view.animate() + .scaleX(BUBBLE_SWAP_SCALE) + .scaleY(BUBBLE_SWAP_SCALE) + .translationY(swapY) + .setDuration(BUBBLE_SWAP_DURATION) + .withEndAction(() -> finishSwapAnimation(view, oldIndex, newIndex, finishReorder)); + } + + private void finishSwapAnimation(View view, int oldIndex, int newIndex, + Runnable finishReorder) { + + // At this point, swapping bubbles have the least overlap. + // Update z-index and badge visibility here for least jarring transition. + view.setZ((mMaxBubbles * mElevation) - newIndex); + BadgedImageView bv = (BadgedImageView) view; + if (oldIndex == 0 && newIndex > 0) { + bv.hideDotAndBadge(!isStackOnLeftSide()); + } else if (oldIndex > 0 && newIndex == 0) { + bv.showDotAndBadge(!isStackOnLeftSide()); + } + + // Animate bubble back into stack, at new index and original size. + final float newY = getStackPosition().y + newIndex * mStackOffset; + view.animate() + .scaleX(1f) + .scaleY(1f) + .translationY(newY) + .setDuration(BUBBLE_SWAP_DURATION) + .withEndAction(() -> finishReorder.run()); + } + @Override void onChildReordered(View child, int oldIndex, int newIndex) { if (isStackPositionSet()) { @@ -781,6 +837,9 @@ public class StackAnimationController extends void onActiveControllerForLayout(PhysicsAnimationLayout layout) { Resources res = layout.getResources(); mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset); + mSwapAnimationOffset = res.getDimensionPixelSize(R.dimen.bubble_swap_animation_offset); + mMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered); + mElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation); mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size); mBubbleBitmapSize = res.getDimensionPixelSize(R.dimen.bubble_bitmap_size); mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);