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

Commit 233218f9 authored by Lyn Han's avatar Lyn Han
Browse files

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
parent 9810e057
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -1218,6 +1218,8 @@
    <dimen name="bubble_message_padding">4dp</dimen>
    <!-- Offset between bubbles in their stacked position. -->
    <dimen name="bubble_stack_offset">10dp</dimen>
    <!-- Offset between stack y and animation y for bubble swap. -->
    <dimen name="bubble_swap_animation_offset">15dp</dimen>
    <!-- How far offscreen the bubble stack rests. Cuts off padding and part of icon bitmap. -->
    <dimen name="bubble_stack_offscreen">9dp</dimen>
    <!-- How far down the screen the stack starts. -->
+12 −0
Original line number Diff line number Diff line
@@ -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.
     */
+20 −13
Original line number Diff line number Diff line
@@ -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<Bubble> bubbles) {
        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<View> bubbleViews = bubbles.stream()
                    .map(b -> b.getIconView()).collect(Collectors.toList());
            mStackAnimationController.animateReorder(bubbleViews, reorder);
        }
        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);
            }
        }
    }
+59 −0
Original line number Diff line number Diff line
@@ -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<View> 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);