Merge "Ensure the bubble expanded view aligns with the pointer" into sc-v2-dev am: cc312e1054

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/15144071

Change-Id: I69abce0f9bcd5e7ffecdc2943b82834dbe3e1c93
This commit is contained in:
Mady Mellor
2021-06-29 19:04:05 +00:00
committed by Automerger Merge Worker
4 changed files with 151 additions and 74 deletions

View File

@@ -25,6 +25,7 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW; import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
@@ -60,7 +61,6 @@ import android.widget.LinearLayout;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.android.internal.policy.ScreenDecorationsUtils; import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.launcher3.icons.IconNormalizer;
import com.android.wm.shell.R; import com.android.wm.shell.R;
import com.android.wm.shell.TaskView; import com.android.wm.shell.TaskView;
import com.android.wm.shell.common.AlphaOptimizedButton; import com.android.wm.shell.common.AlphaOptimizedButton;
@@ -77,7 +77,6 @@ public class BubbleExpandedView extends LinearLayout {
// The triangle pointing to the expanded view // The triangle pointing to the expanded view
private View mPointerView; private View mPointerView;
private int mPointerMargin;
@Nullable private int[] mExpandedViewContainerLocation; @Nullable private int[] mExpandedViewContainerLocation;
private AlphaOptimizedButton mManageButton; private AlphaOptimizedButton mManageButton;
@@ -102,9 +101,6 @@ public class BubbleExpandedView extends LinearLayout {
*/ */
private boolean mIsAlphaAnimating = false; private boolean mIsAlphaAnimating = false;
private int mMinHeight;
private int mOverflowHeight;
private int mManageButtonHeight;
private int mPointerWidth; private int mPointerWidth;
private int mPointerHeight; private int mPointerHeight;
private float mPointerRadius; private float mPointerRadius;
@@ -348,12 +344,8 @@ public class BubbleExpandedView extends LinearLayout {
void updateDimensions() { void updateDimensions() {
Resources res = getResources(); Resources res = getResources();
mMinHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height);
mOverflowHeight = res.getDimensionPixelSize(R.dimen.bubble_overflow_height);
updateFontSize(); updateFontSize();
mPointerMargin = res.getDimensionPixelSize(R.dimen.bubble_pointer_margin);
mPointerWidth = res.getDimensionPixelSize(R.dimen.bubble_pointer_width); mPointerWidth = res.getDimensionPixelSize(R.dimen.bubble_pointer_width);
mPointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height); mPointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height);
mPointerRadius = getResources().getDimensionPixelSize(R.dimen.bubble_pointer_radius); mPointerRadius = getResources().getDimensionPixelSize(R.dimen.bubble_pointer_radius);
@@ -369,7 +361,6 @@ public class BubbleExpandedView extends LinearLayout {
updatePointerView(); updatePointerView();
} }
mManageButtonHeight = res.getDimensionPixelSize(R.dimen.bubble_manage_button_height);
if (mManageButton != null) { if (mManageButton != null) {
int visibility = mManageButton.getVisibility(); int visibility = mManageButton.getVisibility();
removeView(mManageButton); removeView(mManageButton);
@@ -633,12 +624,11 @@ public class BubbleExpandedView extends LinearLayout {
} }
if ((mBubble != null && mTaskView != null) || mIsOverflow) { if ((mBubble != null && mTaskView != null) || mIsOverflow) {
float desiredHeight = mIsOverflow float desiredHeight = mPositioner.getExpandedViewHeight(mBubble);
? mPositioner.isLargeScreen() ? getMaxExpandedHeight() : mOverflowHeight int maxHeight = mPositioner.getMaxExpandedViewHeight(mIsOverflow);
: mBubble.getDesiredHeight(mContext); float height = desiredHeight == MAX_HEIGHT
desiredHeight = Math.max(desiredHeight, mMinHeight); ? maxHeight
float height = Math.min(desiredHeight, getMaxExpandedHeight()); : Math.min(desiredHeight, maxHeight);
height = Math.max(height, mMinHeight);
FrameLayout.LayoutParams lp = mIsOverflow FrameLayout.LayoutParams lp = mIsOverflow
? (FrameLayout.LayoutParams) mOverflowView.getLayoutParams() ? (FrameLayout.LayoutParams) mOverflowView.getLayoutParams()
: (FrameLayout.LayoutParams) mTaskView.getLayoutParams(); : (FrameLayout.LayoutParams) mTaskView.getLayoutParams();
@@ -662,23 +652,6 @@ public class BubbleExpandedView extends LinearLayout {
} }
} }
private int getMaxExpandedHeight() {
int expandedContainerY = mExpandedViewContainerLocation != null
// Remove top insets back here because availableRect.height would account for that
? mExpandedViewContainerLocation[1] - mPositioner.getInsets().top
: 0;
int settingsHeight = mIsOverflow ? 0 : mManageButtonHeight;
int pointerHeight = mPositioner.showBubblesVertically()
? mPointerWidth
: (int) (mPointerHeight - mPointerOverlap + mPointerMargin);
return mPositioner.getAvailableRect().height()
- expandedContainerY
- getPaddingTop()
- getPaddingBottom()
- settingsHeight
- pointerHeight;
}
/** /**
* Update appearance of the expanded view being displayed. * Update appearance of the expanded view being displayed.
* *
@@ -728,14 +701,11 @@ public class BubbleExpandedView extends LinearLayout {
: mPointerHeight - mPointerOverlap; : mPointerHeight - mPointerOverlap;
setPadding((int) paddingLeft, (int) paddingTop, (int) paddingRight, 0); setPadding((int) paddingLeft, (int) paddingTop, (int) paddingRight, 0);
final float expandedViewY = mPositioner.getExpandedViewY(); // Subtract the expandedViewY here because the pointer is placed within the expandedView.
// TODO: I don't understand why it works but it does - why normalized in portrait float pointerPosition = mPositioner.getPointerPosition(bubblePosition);
// & not in landscape? Am I missing ~2dp in the portrait expandedViewY calculation? final float bubbleCenter = mPositioner.showBubblesVertically()
final float normalizedSize = IconNormalizer.getNormalizedCircleSize( ? pointerPosition - mPositioner.getExpandedViewY(mBubble, bubblePosition)
mPositioner.getBubbleSize()); : pointerPosition;
final float bubbleCenter = showVertically
? bubblePosition + (mPositioner.getBubbleSize() / 2f) - expandedViewY
: bubblePosition + (normalizedSize / 2f) - mPointerWidth;
// Post because we need the width of the view // Post because we need the width of the view
post(() -> { post(() -> {
float pointerY; float pointerY;

View File

@@ -34,6 +34,7 @@ import android.view.WindowMetrics;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import com.android.launcher3.icons.IconNormalizer;
import com.android.wm.shell.R; import com.android.wm.shell.R;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
@@ -58,6 +59,8 @@ public class BubblePositioner {
/** When the bubbles are collapsed in a stack only some of them are shown, this is how many. **/ /** When the bubbles are collapsed in a stack only some of them are shown, this is how many. **/
public static final int NUM_VISIBLE_WHEN_RESTING = 2; public static final int NUM_VISIBLE_WHEN_RESTING = 2;
/** Indicates a bubble's height should be the maximum available space. **/
public static final int MAX_HEIGHT = -1;
private Context mContext; private Context mContext;
private WindowManager mWindowManager; private WindowManager mWindowManager;
@@ -68,13 +71,16 @@ public class BubblePositioner {
private int mMaxBubbles; private int mMaxBubbles;
private int mBubbleSize; private int mBubbleSize;
private int mBubbleBadgeSize;
private int mSpacingBetweenBubbles; private int mSpacingBetweenBubbles;
private int mExpandedViewLargeScreenWidth; private int mExpandedViewLargeScreenWidth;
private int mExpandedViewPadding; private int mExpandedViewPadding;
private int mPointerMargin; private int mPointerMargin;
private float mPointerWidth; private int mPointerWidth;
private float mPointerHeight; private int mPointerHeight;
private int mPointerOverlap;
private int mManageButtonHeight;
private int mExpandedViewMinHeight;
private int mOverflowHeight;
private PointF mPinLocation; private PointF mPinLocation;
private PointF mRestingStackPosition; private PointF mRestingStackPosition;
@@ -151,7 +157,6 @@ public class BubblePositioner {
Resources res = mContext.getResources(); Resources res = mContext.getResources();
mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size); mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size);
mBubbleBadgeSize = res.getDimensionPixelSize(R.dimen.bubble_badge_size);
mSpacingBetweenBubbles = res.getDimensionPixelSize(R.dimen.bubble_spacing); mSpacingBetweenBubbles = res.getDimensionPixelSize(R.dimen.bubble_spacing);
mDefaultMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered); mDefaultMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered);
@@ -161,6 +166,10 @@ public class BubblePositioner {
mPointerWidth = res.getDimensionPixelSize(R.dimen.bubble_pointer_width); mPointerWidth = res.getDimensionPixelSize(R.dimen.bubble_pointer_width);
mPointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height); mPointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height);
mPointerMargin = res.getDimensionPixelSize(R.dimen.bubble_pointer_margin); mPointerMargin = res.getDimensionPixelSize(R.dimen.bubble_pointer_margin);
mPointerOverlap = res.getDimensionPixelSize(R.dimen.bubble_pointer_overlap);
mManageButtonHeight = res.getDimensionPixelSize(R.dimen.bubble_manage_button_height);
mExpandedViewMinHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height);
mOverflowHeight = res.getDimensionPixelSize(R.dimen.bubble_overflow_height);
mMaxBubbles = calculateMaxBubbles(); mMaxBubbles = calculateMaxBubbles();
@@ -296,8 +305,8 @@ public class BubblePositioner {
return mPaddings; return mPaddings;
} }
/** Calculates the y position of the expanded view when it is expanded. */ /** Gets the y position of the expanded view if it was top-aligned. */
public float getExpandedViewY() { private float getExpandedViewYTopAligned() {
final int top = getAvailableRect().top; final int top = getAvailableRect().top;
if (showBubblesVertically()) { if (showBubblesVertically()) {
return top - mPointerWidth; return top - mPointerWidth;
@@ -306,6 +315,116 @@ public class BubblePositioner {
} }
} }
/** The maximum height the expanded view can be. */
public int getMaxExpandedViewHeight(boolean isOverflow) {
int paddingTop = showBubblesVertically()
? 0
: mPointerHeight;
int settingsHeight = isOverflow ? 0 : mManageButtonHeight;
// Subtract pointer size because it's laid out in LinearLayout with the expanded view.
int pointerSize = showBubblesVertically()
? mPointerWidth
: (mPointerHeight + mPointerMargin);
// Subtract top insets because availableRect.height would account for that
int expandedContainerY = (int) getExpandedViewYTopAligned() - getInsets().top;
return getAvailableRect().height()
- expandedContainerY
- paddingTop
- settingsHeight
- pointerSize;
}
/**
* Determines the height for the bubble, ensuring a minimum height. If the height should be as
* big as available, returns {@link #MAX_HEIGHT}.
*/
public float getExpandedViewHeight(BubbleViewProvider bubble) {
boolean isOverflow = bubble == null || BubbleOverflow.KEY.equals(bubble.getKey());
float desiredHeight = isOverflow
? mOverflowHeight
: ((Bubble) bubble).getDesiredHeight(mContext);
int manageButtonHeight = isOverflow ? 0 : mManageButtonHeight;
desiredHeight = Math.max(manageButtonHeight + desiredHeight, mExpandedViewMinHeight);
if (desiredHeight > getMaxExpandedViewHeight(isOverflow)) {
return MAX_HEIGHT;
}
return desiredHeight;
}
/**
* Gets the y position for the expanded view. This is the position on screen of the top
* horizontal line of the expanded view.
*
* @param bubble the bubble being positioned.
* @param bubblePosition the x position of the bubble if showing on top, the y position of the
* bubble if showing vertically.
* @return the y position for the expanded view.
*/
public float getExpandedViewY(BubbleViewProvider bubble, float bubblePosition) {
boolean isOverflow = bubble == null || BubbleOverflow.KEY.equals(bubble.getKey());
float expandedViewHeight = getExpandedViewHeight(bubble);
float topAlignment = getExpandedViewYTopAligned();
if (!showBubblesVertically() || expandedViewHeight == MAX_HEIGHT) {
// Top-align when bubbles are shown at the top or are max size.
return topAlignment;
}
// If we're here, we're showing vertically & developer has made height less than maximum.
int manageButtonHeight = isOverflow ? 0 : mManageButtonHeight;
float pointerPosition = getPointerPosition(bubblePosition);
float bottomIfCentered = pointerPosition + (expandedViewHeight / 2) + manageButtonHeight;
float topIfCentered = pointerPosition - (expandedViewHeight / 2);
if (topIfCentered > mPositionRect.top && mPositionRect.bottom > bottomIfCentered) {
// Center it
return pointerPosition - mPointerWidth - (expandedViewHeight / 2f);
} else if (topIfCentered <= mPositionRect.top) {
// Top align
return topAlignment;
} else {
// Bottom align
return mPositionRect.bottom - manageButtonHeight - expandedViewHeight - mPointerWidth;
}
}
/**
* The position the pointer points to, the center of the bubble.
*
* @param bubblePosition the x position of the bubble if showing on top, the y position of the
* bubble if showing vertically.
* @return the position the tip of the pointer points to. The x position if showing on top, the
* y position if showing vertically.
*/
public float getPointerPosition(float bubblePosition) {
// TODO: I don't understand why it works but it does - why normalized in portrait
// & not in landscape? Am I missing ~2dp in the portrait expandedViewY calculation?
final float normalizedSize = IconNormalizer.getNormalizedCircleSize(
getBubbleSize());
return showBubblesVertically()
? bubblePosition + (getBubbleSize() / 2f)
: bubblePosition + (normalizedSize / 2f) - mPointerWidth;
}
/**
* When bubbles are expanded in portrait, they display at the top of the screen in a horizontal
* row. When in landscape or on a large screen, they show at the left or right side in a
* vertical row. This method accounts for screen orientation and will return an x or y value
* for the position of the bubble in the row.
*
* @param index bubble index in the row.
* @param numberOfBubbles the number of bubbles (including the overflow) in the row.
* @return the y position of the bubble if showing vertically and the x position if showing
* horizontally.
*/
public float getBubbleXOrYForOrientation(int index, int numberOfBubbles) {
final float positionInBar = index * (mBubbleSize + mSpacingBetweenBubbles);
final float expandedStackSize = (numberOfBubbles * mBubbleSize)
+ ((numberOfBubbles - 1) * mSpacingBetweenBubbles);
final float centerPosition = showBubblesVertically()
? mPositionRect.centerY()
: mPositionRect.centerX();
final float rowStart = centerPosition - (expandedStackSize / 2f);
return rowStart + positionInBar;
}
/** /**
* Sets the stack's most recent position along the edge of the screen. This is saved when the * Sets the stack's most recent position along the edge of the screen. This is saved when the
* last bubble is removed, so that the stack can be restored in its previous position. * last bubble is removed, so that the stack can be restored in its previous position.

View File

@@ -874,8 +874,10 @@ public class BubbleStackView extends FrameLayout
mExpandedAnimationController.expandFromStack(() -> { mExpandedAnimationController.expandFromStack(() -> {
afterExpandedViewAnimation(); afterExpandedViewAnimation();
} /* after */); } /* after */);
final float translationY = mPositioner.getExpandedViewY(mExpandedBubble,
getBubbleIndex(mExpandedBubble));
mExpandedViewContainer.setTranslationX(0f); mExpandedViewContainer.setTranslationX(0f);
mExpandedViewContainer.setTranslationY(mPositioner.getExpandedViewY()); mExpandedViewContainer.setTranslationY(translationY);
mExpandedViewContainer.setAlpha(1f); mExpandedViewContainer.setAlpha(1f);
} }
removeOnLayoutChangeListener(mOrientationChangedListener); removeOnLayoutChangeListener(mOrientationChangedListener);
@@ -1524,6 +1526,7 @@ public class BubbleStackView extends FrameLayout
bubble.cleanupViews(); bubble.cleanupViews();
} }
updatePointerPosition(); updatePointerPosition();
updateExpandedView();
logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED); logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
return; return;
} }
@@ -1815,9 +1818,10 @@ public class BubbleStackView extends FrameLayout
mTaskbarScrim.setVisibility(VISIBLE); mTaskbarScrim.setVisibility(VISIBLE);
mTaskbarScrim.animate().alpha(1f).start(); mTaskbarScrim.animate().alpha(1f).start();
} }
final float translationY = mPositioner.getExpandedViewY(mExpandedBubble,
getBubbleIndex(mExpandedBubble));
mExpandedViewContainer.setTranslationX(0f); mExpandedViewContainer.setTranslationX(0f);
mExpandedViewContainer.setTranslationY(mPositioner.getExpandedViewY()); mExpandedViewContainer.setTranslationY(translationY);
mExpandedViewContainer.setAlpha(1f); mExpandedViewContainer.setAlpha(1f);
int index; int index;
@@ -1866,7 +1870,7 @@ public class BubbleStackView extends FrameLayout
1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT, 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT, 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
bubbleWillBeAt + mBubbleSize / 2f, bubbleWillBeAt + mBubbleSize / 2f,
mPositioner.getExpandedViewY()); translationY);
} }
mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix); mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
@@ -1970,7 +1974,7 @@ public class BubbleStackView extends FrameLayout
mExpandedViewContainerMatrix.setScale( mExpandedViewContainerMatrix.setScale(
1f, 1f, 1f, 1f,
expandingFromBubbleAt + mBubbleSize / 2f, expandingFromBubbleAt + mBubbleSize / 2f,
mPositioner.getExpandedViewY()); mPositioner.getExpandedViewY(mExpandedBubble, index));
} }
mExpandedViewAlphaAnimator.reverse(); mExpandedViewAlphaAnimator.reverse();
@@ -2077,7 +2081,7 @@ public class BubbleStackView extends FrameLayout
1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT, 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT, 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
expandingFromBubbleDestination + mBubbleSize / 2f, expandingFromBubbleDestination + mBubbleSize / 2f,
mPositioner.getExpandedViewY()); mPositioner.getExpandedViewY(mExpandedBubble, expandingFromBubbleDestination));
} }
mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix); mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
@@ -2698,7 +2702,9 @@ public class BubbleStackView extends FrameLayout
mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE); mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE);
} }
if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
mExpandedViewContainer.setTranslationY(mPositioner.getExpandedViewY()); mExpandedViewContainer.setTranslationY(mPositioner.getExpandedViewY(mExpandedBubble,
mExpandedAnimationController.getBubbleXOrYForOrientation(
getBubbleIndex(mExpandedBubble))));
mExpandedViewContainer.setTranslationX(0f); mExpandedViewContainer.setTranslationX(0f);
mExpandedBubble.getExpandedView().updateView( mExpandedBubble.getExpandedView().updateView(
mExpandedViewContainer.getLocationOnScreen()); mExpandedViewContainer.getLocationOnScreen());

View File

@@ -83,12 +83,6 @@ public class ExpandedAnimationController
private float mBubblePaddingTop; private float mBubblePaddingTop;
/** Size of each bubble. */ /** Size of each bubble. */
private float mBubbleSizePx; private float mBubbleSizePx;
/** Max number of bubbles shown in row above expanded view. */
private int mBubblesMaxRendered;
/** Max amount of space to have between bubbles when expanded. */
private int mBubblesMaxSpace;
/** Amount of space between the bubbles when expanded. */
private float mSpaceBetweenBubbles;
/** Whether the expand / collapse animation is running. */ /** Whether the expand / collapse animation is running. */
private boolean mAnimatingExpand = false; private boolean mAnimatingExpand = false;
@@ -211,8 +205,6 @@ public class ExpandedAnimationController
mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top); mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
mStackOffsetPx = res.getDimensionPixelSize(R.dimen.bubble_stack_offset); mStackOffsetPx = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
mBubbleSizePx = mPositioner.getBubbleSize(); mBubbleSizePx = mPositioner.getBubbleSize();
mBubblesMaxRendered = mPositioner.getMaxBubbles();
mSpaceBetweenBubbles = res.getDimensionPixelSize(R.dimen.bubble_spacing);
} }
/** /**
@@ -628,14 +620,13 @@ public class ExpandedAnimationController
} }
} }
// TODO - could move to method on bubblePositioner if mSpaceBetweenBubbles gets moved
/** /**
* When bubbles are expanded in portrait, they display at the top of the screen in a horizontal * When bubbles are expanded in portrait, they display at the top of the screen in a horizontal
* row. When in landscape or on a large screen, they show at the left or right side in a * row. When in landscape or on a large screen, they show at the left or right side in a
* vertical row. This method accounts for screen orientation and will return an x or y value * vertical row. This method accounts for screen orientation and will return an x or y value
* for the position of the bubble in the row. * for the position of the bubble in the row.
* *
* @param index Bubble index in row. * @param index bubble index in the row.
* @return the y position of the bubble if showing vertically and the x position if showing * @return the y position of the bubble if showing vertically and the x position if showing
* horizontally. * horizontally.
*/ */
@@ -643,15 +634,6 @@ public class ExpandedAnimationController
if (mLayout == null) { if (mLayout == null) {
return 0; return 0;
} }
final float positionInBar = index * (mBubbleSizePx + mSpaceBetweenBubbles); return mPositioner.getBubbleXOrYForOrientation(index, mLayout.getChildCount());
Rect availableRect = mPositioner.getAvailableRect();
final boolean isLandscape = mPositioner.showBubblesVertically();
final float expandedStackSize = (mLayout.getChildCount() * mBubbleSizePx)
+ ((mLayout.getChildCount() - 1) * mSpaceBetweenBubbles);
final float centerPosition = isLandscape
? availableRect.centerY()
: availableRect.centerX();
final float rowStart = centerPosition - (expandedStackSize / 2f);
return rowStart + positionInBar;
} }
} }