Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f609cbd8 authored by Lyn Han's avatar Lyn Han
Browse files

Animations for new bubble while stack is collapsed

Bug: 170302562
Test: have no bubs, add bub => bub moves up to finalY
Test: have bubs, add bub => new bub moves up, old bubs move down
Test: update existing bub => updated bub swoops above stack, then
      moves down to finalY with other bubs
Test: the above on portrait left/right && landscape left/right

Change-Id: I1f9e38fed9ad66ad243700082981f75418c64bf5
parent ecde596d
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -131,7 +131,11 @@ public class BadgedImageView extends ImageView {
     */
    public void setRenderedBubble(BubbleViewProvider bubble) {
        mBubble = bubble;
        if (mDotSuppressionFlags.contains(SuppressionFlag.BEHIND_STACK)) {
            hideBadge();
        } else {
            showBadge();
        }
        mDotColor = bubble.getDotColor();
        drawDot(bubble.getDotPath());
    }
+12 −9
Original line number Diff line number Diff line
@@ -647,10 +647,12 @@ public class BubbleStackView extends FrameLayout
                } else {
                    // Fling the stack to the edge, and save whether or not it's going to end up on
                    // the left side of the screen.
                    final boolean oldOnLeft = mStackOnLeftOrWillBe;
                    mStackOnLeftOrWillBe =
                            mStackAnimationController.flingStackThenSpringToEdge(
                                    viewInitialX + dx, velX, velY) <= 0;
                    updateBubbleIcons();
                    final boolean updateForCollapsedStack = oldOnLeft != mStackOnLeftOrWillBe;
                    updateBadgesAndZOrder(updateForCollapsedStack);
                    logBubbleEvent(null /* no bubble associated with bubble stack move */,
                            FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__STACK_MOVED);
                }
@@ -1481,7 +1483,7 @@ public class BubbleStackView extends FrameLayout
        };
        if (mIsExpanded) {
            reorder.run();
            updateBubbleIcons();
            updateBadgesAndZOrder(false /* setBadgeForCollapsedStack */);
        } else {
            List<View> bubbleViews = bubbles.stream()
                    .map(b -> b.getIconView()).collect(Collectors.toList());
@@ -2616,30 +2618,31 @@ public class BubbleStackView extends FrameLayout
        }

        mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
        updateBubbleIcons();
        updateBadgesAndZOrder(false /* setBadgeForCollapsedStack */);
    }

    /**
     * Sets the appropriate Z-order, badge, and dot position for each bubble in the stack.
     * Animate dot and badge changes.
     */
    private void updateBubbleIcons() {
    private void updateBadgesAndZOrder(boolean setBadgeForCollapsedStack) {
        int bubbleCount = getBubbleCount();
        for (int i = 0; i < bubbleCount; i++) {
            BadgedImageView bv = (BadgedImageView) mBubbleContainer.getChildAt(i);
            bv.setZ((mMaxBubbles * mBubbleElevation) - i);

            if (mIsExpanded) {
                // If we're not displaying vertically, we always show the badge on the left.
                boolean onLeft = mPositioner.showBubblesVertically() && !mStackOnLeftOrWillBe;
                bv.showDotAndBadge(onLeft);
            } else if (i == 0) {
            } else if (setBadgeForCollapsedStack) {
                if (i == 0) {
                    bv.showDotAndBadge(!mStackOnLeftOrWillBe);
                } else {
                    bv.hideDotAndBadge(!mStackOnLeftOrWillBe);
                }
            }
        }
    }

    private void updatePointerPosition() {
        if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) {
+67 −57
Original line number Diff line number Diff line
@@ -64,10 +64,10 @@ public class StackAnimationController extends

    /** Values to use for animating bubbles in. */
    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 float NEW_BUBBLE_START_SCALE = 0.5f;
    private static final float NEW_BUBBLE_START_Y = 100f;
    private static final long BUBBLE_SWAP_DURATION = 300L;

    /**
@@ -779,48 +779,58 @@ public class StackAnimationController extends
    }

    public void animateReorder(List<View> bubbleViews, Runnable after) {
        // After the bubble going to index 0 springs above stack, update all icons
        // at the same time, to avoid visibly changing bubble order before the animation completes.
        Runnable updateAllIcons = () -> {
            for (int newIndex = 0; newIndex < bubbleViews.size(); newIndex++) {
                View view = bubbleViews.get(newIndex);
                updateBadgesAndZOrder(view, newIndex);
            }
        };

        for (int newIndex = 0; newIndex < bubbleViews.size(); newIndex++) {
            View view = bubbleViews.get(newIndex);
            final int oldIndex = mLayout.indexOfChild(view);
            animateSwap(view, oldIndex, newIndex, after);
            animateSwap(view, oldIndex, newIndex, updateAllIcons, 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
        final ViewPropertyAnimator animator = view.animate()
                .scaleX(BUBBLE_SWAP_SCALE)
                .scaleY(BUBBLE_SWAP_SCALE)
                .translationY(swapY)
    private void animateSwap(View view, int oldIndex, int newIndex,
            Runnable updateAllIcons, Runnable finishReorder) {
        if (newIndex == oldIndex) {
            // Add new bubble to index 0; move existing bubbles down
            updateBadgesAndZOrder(view, newIndex);
            if (newIndex == 0) {
                animateInBubble(view, newIndex);
            } else {
                moveToFinalIndex(view, newIndex, finishReorder);
            }
        } else {
            // Reorder existing bubbles
            if (newIndex == 0) {
                animateToFrontThenUpdateIcons(view, updateAllIcons, finishReorder);
            } else {
                moveToFinalIndex(view, newIndex, finishReorder);
            }
        }
    }

    private void animateToFrontThenUpdateIcons(View v, Runnable updateAllIcons,
            Runnable finishReorder) {
        final ViewPropertyAnimator animator = v.animate()
                .translationY(getStackPosition().y - mSwapAnimationOffset)
                .setDuration(BUBBLE_SWAP_DURATION)
                .withEndAction(() -> {
                    finishSwapAnimation(view, oldIndex, newIndex, finishReorder);
                    updateAllIcons.run();
                    moveToFinalIndex(v, 0 /* index */, finishReorder);
                });
        view.setTag(R.id.reorder_animator_tag, animator);
        v.setTag(R.id.reorder_animator_tag, animator);
    }

    private void finishSwapAnimation(View view, int oldIndex, int newIndex,
    private void moveToFinalIndex(View view, 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;
        final ViewPropertyAnimator animator = view.animate()
                .scaleX(1f)
                .scaleY(1f)
                .translationY(newY)
                .translationY(getStackPosition().y + newIndex * mStackOffset)
                .setDuration(BUBBLE_SWAP_DURATION)
                .withEndAction(() -> {
                    view.setTag(R.id.reorder_animator_tag, null);
@@ -829,13 +839,19 @@ public class StackAnimationController extends
        view.setTag(R.id.reorder_animator_tag, animator);
    }

    @Override
    void onChildReordered(View child, int oldIndex, int newIndex) {
        if (isStackPositionSet()) {
            setStackPosition(mStackPosition);
    private void updateBadgesAndZOrder(View v, int index) {
        v.setZ((mMaxBubbles * mElevation) - index);
        BadgedImageView bv = (BadgedImageView) v;
        if (index == 0) {
            bv.showDotAndBadge(!isStackOnLeftSide());
        } else {
            bv.hideDotAndBadge(!isStackOnLeftSide());
        }
    }

    @Override
    void onChildReordered(View child, int oldIndex, int newIndex) {}

    @Override
    void onActiveControllerForLayout(PhysicsAnimationLayout layout) {
        Resources res = layout.getResources();
@@ -943,35 +959,29 @@ public class StackAnimationController extends
    }

    /** Animates in the given bubble. */
    private void animateInBubble(View child, int index) {
    private void animateInBubble(View v, int index) {
        if (!isActiveController()) {
            return;
        }

        v.setTranslationX(mStackPosition.x);
        final float yOffset =
                getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_Y);

        // Position the new bubble in the correct position, scaled down completely.
        child.setTranslationX(mStackPosition.x);
        child.setTranslationY(mStackPosition.y + yOffset * index);
        child.setScaleX(0f);
        child.setScaleY(0f);

        // Push the subsequent views out of the way, if there are subsequent views.
        if (index + 1 < mLayout.getChildCount()) {
            animationForChildAtIndex(index + 1)
                    .translationY(mStackPosition.y + yOffset * (index + 1))
                    .withStiffness(SpringForce.STIFFNESS_LOW)
                    .start();
        }

        // Scale in the new bubble, slightly delayed.
        animationForChild(child)
        final float endY = mStackPosition.y + yOffset * index;
        final float startY = endY + NEW_BUBBLE_START_Y;
        v.setTranslationY(startY);
        v.setScaleX(NEW_BUBBLE_START_SCALE);
        v.setScaleY(NEW_BUBBLE_START_SCALE);
        v.setAlpha(0f);
        final ViewPropertyAnimator animator = v.animate()
                .translationY(endY)
                .scaleX(1f)
                .scaleY(1f)
                .withStiffness(ANIMATE_IN_STIFFNESS)
                .withStartDelay(mLayout.getChildCount() > 1 ? ANIMATE_IN_START_DELAY : 0)
                .start();
                .alpha(1f)
                .setDuration(BUBBLE_SWAP_DURATION)
                .withEndAction(() -> {
                    v.setTag(R.id.reorder_animator_tag, null);
                });
        v.setTag(R.id.reorder_animator_tag, animator);
    }

    /**