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

Commit 06cbf657 authored by Josh Tsuji's avatar Josh Tsuji Committed by Android (Google) Code Review
Browse files

Merge changes I1360deb0,Id9a9eb85,I7f05f2ee,Ie48da88d into qt-dev

* changes:
  Fix issues with animations when the stack is expanded.
  Prevent a crash when touching the stack while it's flinging out of the dismiss area.
  Remove the max rendered children code.
  Stability and logic fixes to PhysicsAnimationLayout to address bugs that leave Bubbles in a bad state.
parents 11ddc8ea f49ee14a
Loading
Loading
Loading
Loading
+28 −19
Original line number Diff line number Diff line
@@ -330,9 +330,7 @@ public class BubbleStackView extends FrameLayout {
        mSurfaceSynchronizer = synchronizer != null ? synchronizer : DEFAULT_SURFACE_SYNCHRONIZER;

        mBubbleContainer = new PhysicsAnimationLayout(context);
        mBubbleContainer.setMaxRenderedChildren(
                getResources().getInteger(R.integer.bubbles_max_rendered));
        mBubbleContainer.setController(mStackAnimationController);
        mBubbleContainer.setActiveController(mStackAnimationController);
        mBubbleContainer.setElevation(elevation);
        mBubbleContainer.setClipChildren(false);
        addView(mBubbleContainer, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
@@ -728,7 +726,7 @@ public class BubbleStackView extends FrameLayout {
    public void updateBubbleOrder(List<Bubble> bubbles) {
        for (int i = 0; i < bubbles.size(); i++) {
            Bubble bubble = bubbles.get(i);
            mBubbleContainer.moveViewTo(bubble.iconView, i);
            mBubbleContainer.reorderView(bubble.iconView, i);
        }
    }

@@ -908,19 +906,17 @@ public class BubbleStackView extends FrameLayout {
            };

            if (shouldExpand) {
                mBubbleContainer.setController(mExpandedAnimationController);
                mExpandedAnimationController.expandFromStack(
                        mStackAnimationController.getStackPositionAlongNearestHorizontalEdge()
                        /* collapseTo */,
                        () -> {
                mBubbleContainer.setActiveController(mExpandedAnimationController);
                mExpandedAnimationController.expandFromStack(() -> {
                    updatePointerPosition();
                    updateAfter.run();
                } /* after */);
            } else {
                mBubbleContainer.cancelAllAnimations();
                mExpandedAnimationController.collapseBackToStack(
                        mStackAnimationController.getStackPositionAlongNearestHorizontalEdge(),
                        () -> {
                            mBubbleContainer.setController(mStackAnimationController);
                            mBubbleContainer.setActiveController(mStackAnimationController);
                            updateAfter.run();
                        });
            }
@@ -1013,7 +1009,7 @@ public class BubbleStackView extends FrameLayout {
        }

        mStackAnimationController.cancelStackPositionAnimations();
        mBubbleContainer.setController(mStackAnimationController);
        mBubbleContainer.setActiveController(mStackAnimationController);
        hideFlyoutImmediate();

        mDraggingInDismissTarget = false;
@@ -1111,6 +1107,10 @@ public class BubbleStackView extends FrameLayout {
    /** Called when a gesture is completed or cancelled. */
    void onGestureFinished() {
        mIsGestureInProgress = false;

        if (mIsExpanded) {
            mExpandedAnimationController.onGestureFinished();
        }
    }

    /** Prepares and starts the desaturate/darken animation on the bubble stack. */
@@ -1201,6 +1201,7 @@ public class BubbleStackView extends FrameLayout {
     */
    void magnetToStackIfNeededThenAnimateDismissal(
            View touchedView, float velX, float velY, Runnable after) {
        final View draggedOutBubble = mExpandedAnimationController.getDraggedOutBubble();
        final Runnable animateDismissal = () -> {
            mAfterMagnet = null;

@@ -1218,7 +1219,7 @@ public class BubbleStackView extends FrameLayout {
                            resetDesaturationAndDarken();
                        });
            } else {
                mExpandedAnimationController.dismissDraggedOutBubble(() -> {
                mExpandedAnimationController.dismissDraggedOutBubble(draggedOutBubble, () -> {
                    mAnimatingMagnet = false;
                    mShowingDismiss = false;
                    mDraggingInDismissTarget = false;
@@ -1385,10 +1386,18 @@ public class BubbleStackView extends FrameLayout {
                };

                // Post in case layout isn't complete and getWidth returns 0.
                post(() -> mFlyout.showFlyout(
                post(() -> {
                    // An auto-expanding bubble could have been posted during the time it takes to
                    // layout.
                    if (isExpanded()) {
                        return;
                    }

                    mFlyout.showFlyout(
                            updateMessage, mStackAnimationController.getStackPosition(), getWidth(),
                            mStackAnimationController.isStackOnLeftSide(),
                        bubble.iconView.getBadgeColor(), mAfterFlyoutHides));
                            bubble.iconView.getBadgeColor(), mAfterFlyoutHides);
                });
            }

            mFlyout.removeCallbacks(mHideFlyout);
+98 −74
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.graphics.PointF;
import android.view.View;
import android.view.WindowInsets;

import androidx.annotation.Nullable;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.SpringForce;

@@ -63,12 +64,16 @@ public class ExpandedAnimationController
    private Point mDisplaySize;
    /** Size of dismiss target at bottom of screen. */
    private float mPipDismissHeight;
    /** Max number of bubbles shown in row above expanded view.*/
    private int mBubblesMaxRendered;

    /** Whether the dragged-out bubble is in the dismiss target. */
    private boolean mIndividualBubbleWithinDismissTarget = false;

    private boolean mAnimatingExpand = false;
    private boolean mAnimatingCollapse = false;
    private Runnable mAfterExpand;
    private Runnable mAfterCollapse;
    private PointF mCollapsePoint;

    /**
     * Whether the dragged out bubble is springing towards the touch point, rather than using the
     * default behavior of moving directly to the touch point.
@@ -97,56 +102,60 @@ public class ExpandedAnimationController
    private View mBubbleDraggingOut;

    /**
     * Drag velocities for the dragging-out bubble when the drag finished. These are used by
     * {@link #onChildRemoved} to animate out the bubble while respecting touch velocity.
     * Animates expanding the bubbles into a row along the top of the screen.
     */
    private float mBubbleDraggingOutVelX;
    private float mBubbleDraggingOutVelY;
    public void expandFromStack(Runnable after) {
        mAnimatingCollapse = false;
        mAnimatingExpand = true;
        mAfterExpand = after;

    @Override
    protected void setLayout(PhysicsAnimationLayout layout) {
        super.setLayout(layout);
        startOrUpdateExpandAnimation();
    }

        final Resources res = layout.getResources();
        mStackOffsetPx = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
        mBubblePaddingPx = res.getDimensionPixelSize(R.dimen.bubble_padding);
        mBubbleSizePx = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
        mStatusBarHeight =
                res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
        mPipDismissHeight = res.getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height);
        mBubblesMaxRendered = res.getInteger(R.integer.bubbles_max_rendered);
    /** Animate collapsing the bubbles back to their stacked position. */
    public void collapseBackToStack(PointF collapsePoint, Runnable after) {
        mAnimatingExpand = false;
        mAnimatingCollapse = true;
        mAfterCollapse = after;
        mCollapsePoint = collapsePoint;

        startOrUpdateCollapseAnimation();
    }

    /**
     * Animates expanding the bubbles into a row along the top of the screen.
     */
    public void expandFromStack(PointF collapseTo, Runnable after) {
    private void startOrUpdateExpandAnimation() {
        animationsForChildrenFromIndex(
                0, /* startIndex */
                new ChildAnimationConfigurator() {
                    @Override
                    public void configureAnimationForChildAtIndex(
                            int index, PhysicsAnimationLayout.PhysicsPropertyAnimator animation) {
                        animation.position(getBubbleLeft(index), getExpandedY());
                (index, animation) -> animation.position(getBubbleLeft(index), getExpandedY()))
                .startAll(() -> {
                    mAnimatingExpand = false;

                    if (mAfterExpand != null) {
                        mAfterExpand.run();
                    }
            })
            .startAll(after);

        mCollapseToPoint = collapseTo;
                    mAfterExpand = null;
                });
    }

    /** Animate collapsing the bubbles back to their stacked position. */
    public void collapseBackToStack(Runnable after) {
    private void startOrUpdateCollapseAnimation() {
        // Stack to the left if we're going to the left, or right if not.
        final float sideMultiplier = mLayout.isFirstChildXLeftOfCenter(mCollapseToPoint.x) ? -1 : 1;

        final float sideMultiplier = mLayout.isFirstChildXLeftOfCenter(mCollapsePoint.x) ? -1 : 1;
        animationsForChildrenFromIndex(
                0, /* startIndex */
                (index, animation) ->
                (index, animation) -> {
                    animation.position(
                            mCollapseToPoint.x + (sideMultiplier * index * mStackOffsetPx),
                            mCollapseToPoint.y))
            .startAll(after /* endAction */);
                            mCollapsePoint.x + (sideMultiplier * index * mStackOffsetPx),
                            mCollapsePoint.y);
                })
                .startAll(() -> {
                    mAnimatingCollapse = false;

                    if (mAfterCollapse != null) {
                        mAfterCollapse.run();
                    }

                    mAfterCollapse = null;
                });
    }

    /** Prepares the given bubble to be dragged out. */
@@ -190,10 +199,10 @@ public class ExpandedAnimationController
    }

    /** Plays a dismiss animation on the dragged out bubble. */
    public void dismissDraggedOutBubble(Runnable after) {
    public void dismissDraggedOutBubble(View bubble, Runnable after) {
        mIndividualBubbleWithinDismissTarget = false;

        animationForChild(mBubbleDraggingOut)
        animationForChild(bubble)
                .withStiffness(SpringForce.STIFFNESS_HIGH)
                .scaleX(1.1f)
                .scaleY(1.1f)
@@ -203,6 +212,10 @@ public class ExpandedAnimationController
        updateBubblePositions();
    }

    @Nullable public View getDraggedOutBubble() {
        return mBubbleDraggingOut;
    }

    /** Magnets the given bubble to the dismiss target. */
    public void magnetBubbleToDismiss(
            View bubbleView, float velX, float velY, float destY, Runnable after) {
@@ -245,20 +258,13 @@ public class ExpandedAnimationController
                .withPositionStartVelocities(velX, velY)
                .start(() -> bubbleView.setTranslationZ(0f) /* after */);

        mBubbleDraggingOut = null;
        mBubbleDraggedOutEnough = false;
        updateBubblePositions();
    }

    /**
     * Sets configuration variables so that when the given bubble is removed, the animations are
     * started with the given velocities.
     */
    public void prepareForDismissalWithVelocity(View bubbleView, float velX, float velY) {
        mBubbleDraggingOut = bubbleView;
        mBubbleDraggingOutVelX = velX;
        mBubbleDraggingOutVelY = velY;
    /** Resets bubble drag out gesture flags. */
    public void onGestureFinished() {
        mBubbleDraggedOutEnough = false;
        mBubbleDraggingOut = null;
    }

    /**
@@ -296,6 +302,23 @@ public class ExpandedAnimationController
                : 0);
    }

    @Override
    void onActiveControllerForLayout(PhysicsAnimationLayout layout) {
        final Resources res = layout.getResources();
        mStackOffsetPx = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
        mBubblePaddingPx = res.getDimensionPixelSize(R.dimen.bubble_padding);
        mBubbleSizePx = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
        mStatusBarHeight =
                res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
        mPipDismissHeight = res.getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height);

        // Ensure that all child views are at 1x scale, and visible, in case they were animating
        // in.
        mLayout.setVisibility(View.VISIBLE);
        animationsForChildrenFromIndex(0 /* startIndex */, (index, animation) ->
                animation.scaleX(1f).scaleY(1f).alpha(1f)).startAll();
    }

    @Override
    Set<DynamicAnimation.ViewProperty> getAnimatedProperties() {
        return Sets.newHashSet(
@@ -325,8 +348,14 @@ public class ExpandedAnimationController

    @Override
    void onChildAdded(View child, int index) {
        // If a bubble is added while the expand/collapse animations are playing, update the
        // animation to include the new bubble.
        if (mAnimatingExpand) {
            startOrUpdateExpandAnimation();
        } else if (mAnimatingCollapse) {
            startOrUpdateCollapseAnimation();
        } else {
            child.setTranslationX(getXForChildAtIndex(index));

            animationForChild(child)
                    .translationY(
                            getExpandedY() - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR, /* from */
@@ -334,6 +363,7 @@ public class ExpandedAnimationController
                    .start();
            updateBubblePositions();
        }
    }

    @Override
    void onChildRemoved(View child, int index, Runnable finishRemoval) {
@@ -357,19 +387,15 @@ public class ExpandedAnimationController
    }

    @Override
    protected void setChildVisibility(View child, int index, int visibility) {
        if (visibility == View.VISIBLE) {
            // Set alpha to 0 but then become visible immediately so the animation is visible.
            child.setAlpha(0f);
            child.setVisibility(View.VISIBLE);
    void onChildReordered(View child, int oldIndex, int newIndex) {
        updateBubblePositions();
    }

        animationForChild(child)
                .alpha(visibility == View.GONE ? 0f : 1f)
                .start(() -> super.setChildVisibility(child, index, visibility) /* after */);
    private void updateBubblePositions() {
        if (mAnimatingExpand || mAnimatingCollapse) {
            return;
        }

    private void updateBubblePositions() {
        for (int i = 0; i < mLayout.getChildCount(); i++) {
            final View bubble = mLayout.getChildAt(i);

@@ -378,6 +404,7 @@ public class ExpandedAnimationController
            if (bubble.equals(mBubbleDraggingOut)) {
                return;
            }

            animationForChild(bubble)
                    .translationX(getBubbleLeft(i))
                    .start();
@@ -403,10 +430,7 @@ public class ExpandedAnimationController
            return 0;
        }
        int bubbleCount = mLayout.getChildCount();
        if (bubbleCount > mBubblesMaxRendered) {
            // Only shown bubbles are relevant for calculating position.
            bubbleCount = mBubblesMaxRendered;
        }

        // Width calculations.
        double bubble = bubbleCount * mBubbleSizePx;
        float gap = (bubbleCount - 1) * mBubblePaddingPx;
+136 −124

File changed.

Preview size limit exceeded, changes collapsed.

+43 −28
Original line number Diff line number Diff line
@@ -154,21 +154,6 @@ public class StackAnimationController extends
    /** Height of the status bar. */
    private float mStatusBarHeight;

    @Override
    protected void setLayout(PhysicsAnimationLayout layout) {
        super.setLayout(layout);

        Resources res = layout.getResources();
        mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
        mIndividualBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
        mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding);
        mBubbleOffscreen = res.getDimensionPixelSize(R.dimen.bubble_stack_offscreen);
        mStackStartingVerticalOffset =
                res.getDimensionPixelSize(R.dimen.bubble_stack_starting_offset_y);
        mStatusBarHeight =
                res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
    }

    /**
     * Instantly move the first bubble to the given point, and animate the rest of the stack behind
     * it with the 'following' effect.
@@ -286,6 +271,8 @@ public class StackAnimationController extends
                },
                DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);

        // If we're flinging now, there's no more touch event to catch up to.
        mFirstBubbleSpringingToTouch = false;
        mIsMovingFromFlinging = true;
        return destinationRelativeX;
    }
@@ -656,7 +643,27 @@ public class StackAnimationController extends

        if (mLayout.getChildCount() > 0) {
            animationForChildAtIndex(0).translationX(mStackPosition.x).start();
        } else {
            // Set the start position back to the default since we're out of bubbles. New bubbles
            // will then animate in from the start position.
            mStackPosition = getDefaultStartPosition();
        }
    }

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

    @Override
    void onActiveControllerForLayout(PhysicsAnimationLayout layout) {
        Resources res = layout.getResources();
        mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
        mIndividualBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
        mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding);
        mBubbleOffscreen = res.getDimensionPixelSize(R.dimen.bubble_stack_offscreen);
        mStackStartingVerticalOffset =
                res.getDimensionPixelSize(R.dimen.bubble_stack_starting_offset_y);
        mStatusBarHeight =
                res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
    }

    /** Moves the stack, without any animation, to the starting position. */
@@ -664,11 +671,10 @@ public class StackAnimationController extends
        // Post to ensure that the layout's width and height have been calculated.
        mLayout.setVisibility(View.INVISIBLE);
        mLayout.post(() -> {
            mStackMovedToStartPosition = true;
            setStackPosition(
                    mRestingStackPosition == null
            setStackPosition(mRestingStackPosition == null
                    ? getDefaultStartPosition()
                    : mRestingStackPosition);
            mStackMovedToStartPosition = true;
            mLayout.setVisibility(View.VISIBLE);

            // Animate in the top bubble now that we're visible.
@@ -707,17 +713,22 @@ public class StackAnimationController extends
        Log.d(TAG, String.format("Setting position to (%f, %f).", pos.x, pos.y));
        mStackPosition.set(pos.x, pos.y);

        // If we're not the active controller, we don't want to physically move the bubble views.
        if (isActiveController()) {
            mLayout.cancelAllAnimations();
            cancelStackPositionAnimations();

            // Since we're not using the chained animations, apply the offsets manually.
        final float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X);
        final float yOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_Y);
            final float xOffset = getOffsetForChainedPropertyAnimation(
                    DynamicAnimation.TRANSLATION_X);
            final float yOffset = getOffsetForChainedPropertyAnimation(
                    DynamicAnimation.TRANSLATION_Y);
            for (int i = 0; i < mLayout.getChildCount(); i++) {
                mLayout.getChildAt(i).setTranslationX(pos.x + (i * xOffset));
                mLayout.getChildAt(i).setTranslationY(pos.y + (i * yOffset));
            }
        }
    }

    /** Returns the default stack position, which is on the top right. */
    private PointF getDefaultStartPosition() {
@@ -732,6 +743,10 @@ public class StackAnimationController extends

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

        child.setTranslationY(mStackPosition.y);

        float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X);
+9 −19
Original line number Diff line number Diff line
@@ -60,8 +60,8 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC
    @Before
    public void setUp() throws Exception {
        super.setUp();
        addOneMoreThanRenderLimitBubbles();
        mLayout.setController(mExpandedController);
        addOneMoreThanBubbleLimitBubbles();
        mLayout.setActiveController(mExpandedController);

        Resources res = mLayout.getResources();
        mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
@@ -73,14 +73,14 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC
    @Test
    public void testExpansionAndCollapse() throws InterruptedException {
        Runnable afterExpand = Mockito.mock(Runnable.class);
        mExpandedController.expandFromStack(mExpansionPoint, afterExpand);
        mExpandedController.expandFromStack(afterExpand);
        waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);

        testBubblesInCorrectExpandedPositions();
        verify(afterExpand).run();

        Runnable afterCollapse = Mockito.mock(Runnable.class);
        mExpandedController.collapseBackToStack(afterCollapse);
        mExpandedController.collapseBackToStack(mExpansionPoint, afterCollapse);
        waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);

        testStackedAtPosition(mExpansionPoint.x, mExpansionPoint.y, -1);
@@ -139,7 +139,6 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC
        assertEquals(500f, draggedBubble.getTranslationY(), 1f);

        // Snap it back and make sure it made it back correctly.
        mExpandedController.prepareForDismissalWithVelocity(draggedBubble, 0f, 0f);
        mLayout.removeView(draggedBubble);
        waitForLayoutMessageQueue();
        waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
@@ -169,7 +168,7 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC

        // Dismiss the now-magneted bubble, verify that the callback was called.
        final Runnable afterDismiss = Mockito.mock(Runnable.class);
        mExpandedController.dismissDraggedOutBubble(afterDismiss);
        mExpandedController.dismissDraggedOutBubble(draggedOutView, afterDismiss);
        waitForPropertyAnimations(DynamicAnimation.ALPHA);
        verify(after).run();

@@ -224,7 +223,7 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC

    /** Expand the stack and wait for animations to finish. */
    private void expand() throws InterruptedException {
        mExpandedController.expandFromStack(mExpansionPoint, Mockito.mock(Runnable.class));
        mExpandedController.expandFromStack(Mockito.mock(Runnable.class));
        waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
    }

@@ -236,26 +235,19 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC
            assertEquals(x + i * offsetMultiplier * mStackOffset,
                    mLayout.getChildAt(i).getTranslationX(), 2f);
            assertEquals(y, mLayout.getChildAt(i).getTranslationY(), 2f);

            if (i < mMaxRenderedBubbles) {
            assertEquals(1f, mLayout.getChildAt(i).getAlpha(), .01f);
        }
    }
    }

    /** Check that children are in the correct positions for being expanded. */
    private void testBubblesInCorrectExpandedPositions() {
        // Check all the visible bubbles to see if they're in the right place.
        for (int i = 0; i < Math.min(mLayout.getChildCount(), mMaxRenderedBubbles); i++) {
        for (int i = 0; i < mLayout.getChildCount(); i++) {
            assertEquals(getBubbleLeft(i),
                    mLayout.getChildAt(i).getTranslationX(),
                    2f);
            assertEquals(mExpandedController.getExpandedY(),
                    mLayout.getChildAt(i).getTranslationY(), 2f);

            if (i < mMaxRenderedBubbles) {
                assertEquals(1f, mLayout.getChildAt(i).getAlpha(), .01f);
            }
        }
    }

@@ -273,9 +265,7 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC
            return 0;
        }
        int bubbleCount = mLayout.getChildCount();
        if (bubbleCount > mMaxRenderedBubbles) {
            bubbleCount = mMaxRenderedBubbles;
        }

        // Width calculations.
        double bubble = bubbleCount * mBubbleSize;
        float gap = (bubbleCount - 1) * mBubblePadding;
Loading