blob: b69b94c6d00a985b536c452f77841f0818614784 [file] [log] [blame]
Joshua Tsujib1a796b2019-01-16 15:43:12 -08001/*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.bubbles.animation;
18
Joshua Tsujif44347f2019-02-12 14:28:06 -050019import android.content.res.Resources;
Mady Mellor44ee2fe2019-01-30 17:51:16 -080020import android.graphics.Point;
Joshua Tsujib1a796b2019-01-16 15:43:12 -080021import android.graphics.PointF;
22import android.view.View;
23import android.view.WindowInsets;
24
Joshua Tsujif49ee142019-05-29 16:32:01 -040025import androidx.annotation.Nullable;
Joshua Tsujib1a796b2019-01-16 15:43:12 -080026import androidx.dynamicanimation.animation.DynamicAnimation;
27import androidx.dynamicanimation.animation.SpringForce;
28
29import com.android.systemui.R;
30
31import com.google.android.collect.Sets;
32
33import java.util.Set;
34
35/**
36 * Animation controller for bubbles when they're in their expanded state, or animating to/from the
37 * expanded state. This controls the expansion animation as well as bubbles 'dragging out' to be
38 * dismissed.
39 */
40public class ExpandedAnimationController
41 extends PhysicsAnimationLayout.PhysicsAnimationController {
42
43 /**
Joshua Tsuji1575e6b2019-01-30 13:43:28 -050044 * How much to translate the bubbles when they're animating in/out. This value is multiplied by
45 * the bubble size.
46 */
47 private static final int ANIMATE_TRANSLATION_FACTOR = 4;
48
Joshua Tsuji442b6272019-02-08 13:23:43 -050049 /** How much to scale down bubbles when they're animating in/out. */
50 private static final float ANIMATE_SCALE_PERCENT = 0.5f;
51
Joshua Tsuji3829caa2019-03-05 18:09:13 -050052 /** The stack position to collapse back to in {@link #collapseBackToStack}. */
53 private PointF mCollapseToPoint;
Joshua Tsujib1a796b2019-01-16 15:43:12 -080054
55 /** Horizontal offset between bubbles, which we need to know to re-stack them. */
56 private float mStackOffsetPx;
Lyn Han4a8efe32019-05-30 09:43:27 -070057 /** Space between status bar and bubbles in the expanded state. */
58 private float mBubblePaddingTop;
Joshua Tsujib1a796b2019-01-16 15:43:12 -080059 /** Size of each bubble. */
60 private float mBubbleSizePx;
Joshua Tsujif44347f2019-02-12 14:28:06 -050061 /** Height of the status bar. */
62 private float mStatusBarHeight;
Mady Mellor44ee2fe2019-01-30 17:51:16 -080063 /** Size of display. */
64 private Point mDisplaySize;
Lyn Han522e9ff2019-05-17 13:26:13 -070065 /** Max number of bubbles shown in row above expanded view.*/
66 private int mBubblesMaxRendered;
Mady Mellor44ee2fe2019-01-30 17:51:16 -080067
Joshua Tsuji4accf5982019-04-22 17:36:11 -040068 /** Whether the dragged-out bubble is in the dismiss target. */
69 private boolean mIndividualBubbleWithinDismissTarget = false;
70
Joshua Tsujif49ee142019-05-29 16:32:01 -040071 private boolean mAnimatingExpand = false;
72 private boolean mAnimatingCollapse = false;
73 private Runnable mAfterExpand;
74 private Runnable mAfterCollapse;
75 private PointF mCollapsePoint;
76
Joshua Tsuji4accf5982019-04-22 17:36:11 -040077 /**
78 * Whether the dragged out bubble is springing towards the touch point, rather than using the
79 * default behavior of moving directly to the touch point.
80 *
81 * This happens when the user's finger exits the dismiss area while the bubble is magnetized to
82 * the center. Since the touch point differs from the bubble location, we need to animate the
83 * bubble back to the touch point to avoid a jarring instant location change from the center of
84 * the target to the touch point just outside the target bounds.
85 */
86 private boolean mSpringingBubbleToTouch = false;
87
Lyn Han6f6b3ae2019-05-16 14:17:30 -070088 private int mExpandedViewPadding;
Lyn Han1b4f25e2019-06-11 13:56:34 -070089 private float mLauncherGridDiff;
Lyn Han6f6b3ae2019-05-16 14:17:30 -070090
91 public ExpandedAnimationController(Point displaySize, int expandedViewPadding) {
Mady Mellor44ee2fe2019-01-30 17:51:16 -080092 mDisplaySize = displaySize;
Lyn Han6f6b3ae2019-05-16 14:17:30 -070093 mExpandedViewPadding = expandedViewPadding;
Lyn Han1b4f25e2019-06-11 13:56:34 -070094 mLauncherGridDiff = 30f;
Mady Mellor44ee2fe2019-01-30 17:51:16 -080095 }
Joshua Tsujib1a796b2019-01-16 15:43:12 -080096
Joshua Tsuji442b6272019-02-08 13:23:43 -050097 /**
98 * Whether the individual bubble has been dragged out of the row of bubbles far enough to cause
99 * the rest of the bubbles to animate to fill the gap.
100 */
101 private boolean mBubbleDraggedOutEnough = false;
102
103 /** The bubble currently being dragged out of the row (to potentially be dismissed). */
104 private View mBubbleDraggingOut;
105
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800106 /**
107 * Animates expanding the bubbles into a row along the top of the screen.
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800108 */
Joshua Tsujif49ee142019-05-29 16:32:01 -0400109 public void expandFromStack(Runnable after) {
110 mAnimatingCollapse = false;
111 mAnimatingExpand = true;
112 mAfterExpand = after;
Joshua Tsujic1108432019-02-22 16:10:12 -0500113
Joshua Tsujif49ee142019-05-29 16:32:01 -0400114 startOrUpdateExpandAnimation();
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800115 }
116
117 /** Animate collapsing the bubbles back to their stacked position. */
Joshua Tsujif49ee142019-05-29 16:32:01 -0400118 public void collapseBackToStack(PointF collapsePoint, Runnable after) {
119 mAnimatingExpand = false;
120 mAnimatingCollapse = true;
121 mAfterCollapse = after;
122 mCollapsePoint = collapsePoint;
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800123
Joshua Tsujif49ee142019-05-29 16:32:01 -0400124 startOrUpdateCollapseAnimation();
125 }
126
127 private void startOrUpdateExpandAnimation() {
Joshua Tsujic1108432019-02-22 16:10:12 -0500128 animationsForChildrenFromIndex(
129 0, /* startIndex */
Joshua Tsujif49ee142019-05-29 16:32:01 -0400130 (index, animation) -> animation.position(getBubbleLeft(index), getExpandedY()))
131 .startAll(() -> {
132 mAnimatingExpand = false;
133
134 if (mAfterExpand != null) {
135 mAfterExpand.run();
136 }
137
138 mAfterExpand = null;
139 });
140 }
141
142 private void startOrUpdateCollapseAnimation() {
143 // Stack to the left if we're going to the left, or right if not.
144 final float sideMultiplier = mLayout.isFirstChildXLeftOfCenter(mCollapsePoint.x) ? -1 : 1;
145 animationsForChildrenFromIndex(
146 0, /* startIndex */
147 (index, animation) -> {
Joshua Tsujic1108432019-02-22 16:10:12 -0500148 animation.position(
Joshua Tsujif49ee142019-05-29 16:32:01 -0400149 mCollapsePoint.x + (sideMultiplier * index * mStackOffsetPx),
150 mCollapsePoint.y);
151 })
152 .startAll(() -> {
153 mAnimatingCollapse = false;
154
155 if (mAfterCollapse != null) {
156 mAfterCollapse.run();
157 }
158
159 mAfterCollapse = null;
160 });
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800161 }
162
Joshua Tsuji442b6272019-02-08 13:23:43 -0500163 /** Prepares the given bubble to be dragged out. */
164 public void prepareForBubbleDrag(View bubble) {
165 mLayout.cancelAnimationsOnView(bubble);
166
167 mBubbleDraggingOut = bubble;
168 mBubbleDraggingOut.setTranslationZ(Short.MAX_VALUE);
169 }
170
171 /**
172 * Drags an individual bubble to the given coordinates. Bubbles to the right will animate to
173 * take its place once it's dragged out of the row of bubbles, and animate out of the way if the
174 * bubble is dragged back into the row.
175 */
176 public void dragBubbleOut(View bubbleView, float x, float y) {
Joshua Tsuji4accf5982019-04-22 17:36:11 -0400177 if (mSpringingBubbleToTouch) {
178 if (mLayout.arePropertiesAnimatingOnView(
179 bubbleView, DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y)) {
180 animationForChild(mBubbleDraggingOut)
181 .translationX(x)
182 .translationY(y)
183 .withStiffness(SpringForce.STIFFNESS_HIGH)
184 .start();
185 } else {
186 mSpringingBubbleToTouch = false;
187 }
188 }
189
190 if (!mSpringingBubbleToTouch && !mIndividualBubbleWithinDismissTarget) {
191 bubbleView.setTranslationX(x);
192 bubbleView.setTranslationY(y);
193 }
Joshua Tsuji442b6272019-02-08 13:23:43 -0500194
195 final boolean draggedOutEnough =
196 y > getExpandedY() + mBubbleSizePx || y < getExpandedY() - mBubbleSizePx;
197 if (draggedOutEnough != mBubbleDraggedOutEnough) {
Lyn Han522e9ff2019-05-17 13:26:13 -0700198 updateBubblePositions();
Joshua Tsuji442b6272019-02-08 13:23:43 -0500199 mBubbleDraggedOutEnough = draggedOutEnough;
200 }
201 }
202
Joshua Tsuji4accf5982019-04-22 17:36:11 -0400203 /** Plays a dismiss animation on the dragged out bubble. */
Joshua Tsujif49ee142019-05-29 16:32:01 -0400204 public void dismissDraggedOutBubble(View bubble, Runnable after) {
Joshua Tsuji4accf5982019-04-22 17:36:11 -0400205 mIndividualBubbleWithinDismissTarget = false;
206
Joshua Tsujif49ee142019-05-29 16:32:01 -0400207 animationForChild(bubble)
Joshua Tsuji4accf5982019-04-22 17:36:11 -0400208 .withStiffness(SpringForce.STIFFNESS_HIGH)
209 .scaleX(1.1f)
210 .scaleY(1.1f)
211 .alpha(0f, after)
212 .start();
Lyn Han522e9ff2019-05-17 13:26:13 -0700213
214 updateBubblePositions();
Joshua Tsuji4accf5982019-04-22 17:36:11 -0400215 }
216
Joshua Tsujif49ee142019-05-29 16:32:01 -0400217 @Nullable public View getDraggedOutBubble() {
218 return mBubbleDraggingOut;
219 }
220
Joshua Tsuji4accf5982019-04-22 17:36:11 -0400221 /** Magnets the given bubble to the dismiss target. */
222 public void magnetBubbleToDismiss(
223 View bubbleView, float velX, float velY, float destY, Runnable after) {
224 mIndividualBubbleWithinDismissTarget = true;
225 mSpringingBubbleToTouch = false;
226 animationForChild(bubbleView)
227 .withStiffness(SpringForce.STIFFNESS_MEDIUM)
228 .withDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
229 .withPositionStartVelocities(velX, velY)
230 .translationX(mLayout.getWidth() / 2f - mBubbleSizePx / 2f)
231 .translationY(destY, after)
232 .start();
233 }
234
235 /**
236 * Springs the dragged-out bubble towards the given coordinates and sets flags to have touch
237 * events update the spring's final position until it's settled.
238 */
239 public void demagnetizeBubbleTo(float x, float y, float velX, float velY) {
240 mIndividualBubbleWithinDismissTarget = false;
241 mSpringingBubbleToTouch = true;
242
243 animationForChild(mBubbleDraggingOut)
244 .translationX(x)
245 .translationY(y)
246 .withPositionStartVelocities(velX, velY)
247 .withStiffness(SpringForce.STIFFNESS_HIGH)
248 .start();
249 }
250
Joshua Tsuji442b6272019-02-08 13:23:43 -0500251 /**
252 * Snaps a bubble back to its position within the bubble row, and animates the rest of the
253 * bubbles to accommodate it if it was previously dragged out past the threshold.
254 */
255 public void snapBubbleBack(View bubbleView, float velX, float velY) {
256 final int index = mLayout.indexOfChild(bubbleView);
257
Joshua Tsujic1108432019-02-22 16:10:12 -0500258 animationForChildAtIndex(index)
Joshua Tsujif49ee142019-05-29 16:32:01 -0400259 .position(getBubbleLeft(index), getExpandedY())
260 .withPositionStartVelocities(velX, velY)
261 .start(() -> bubbleView.setTranslationZ(0f) /* after */);
Joshua Tsuji442b6272019-02-08 13:23:43 -0500262
Lyn Han522e9ff2019-05-17 13:26:13 -0700263 updateBubblePositions();
Joshua Tsuji442b6272019-02-08 13:23:43 -0500264 }
265
Joshua Tsujif49ee142019-05-29 16:32:01 -0400266 /** Resets bubble drag out gesture flags. */
267 public void onGestureFinished() {
Joshua Tsuji442b6272019-02-08 13:23:43 -0500268 mBubbleDraggedOutEnough = false;
Joshua Tsujif49ee142019-05-29 16:32:01 -0400269 mBubbleDraggingOut = null;
Joshua Tsuji61b38f52019-05-31 16:20:22 -0400270 updateBubblePositions();
Joshua Tsuji442b6272019-02-08 13:23:43 -0500271 }
272
273 /**
Mady Mellor5d8f1402019-02-21 18:23:52 -0800274 * Animates the bubbles to {@link #getExpandedY()} position. Used in response to IME showing.
275 */
276 public void updateYPosition(Runnable after) {
277 if (mLayout == null) return;
Joshua Tsujic1108432019-02-22 16:10:12 -0500278 animationsForChildrenFromIndex(
279 0, (i, anim) -> anim.translationY(getExpandedY())).startAll(after);
Mady Mellor5d8f1402019-02-21 18:23:52 -0800280 }
281
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800282 /** The Y value of the row of expanded bubbles. */
Mady Mellorfe7ec032019-01-30 17:32:49 -0800283 public float getExpandedY() {
Mady Mellor5d8f1402019-02-21 18:23:52 -0800284 if (mLayout == null || mLayout.getRootWindowInsets() == null) {
285 return 0;
286 }
Mady Mellor5d8f1402019-02-21 18:23:52 -0800287 final WindowInsets insets = mLayout.getRootWindowInsets();
Lyn Han4a8efe32019-05-30 09:43:27 -0700288 return mBubblePaddingTop + Math.max(
Lyn Han5aa27e22019-05-15 10:55:07 -0700289 mStatusBarHeight,
290 insets.getDisplayCutout() != null
291 ? insets.getDisplayCutout().getSafeInsetTop()
292 : 0);
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800293 }
294
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800295 @Override
Joshua Tsujic36ee6f2019-05-28 17:00:16 -0400296 void onActiveControllerForLayout(PhysicsAnimationLayout layout) {
297 final Resources res = layout.getResources();
298 mStackOffsetPx = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
Joshua Tsuji61b38f52019-05-31 16:20:22 -0400299 mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
Joshua Tsujic36ee6f2019-05-28 17:00:16 -0400300 mBubbleSizePx = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
301 mStatusBarHeight =
302 res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
Joshua Tsuji61b38f52019-05-31 16:20:22 -0400303 mBubblesMaxRendered = res.getInteger(R.integer.bubbles_max_rendered);
Joshua Tsujic36ee6f2019-05-28 17:00:16 -0400304
305 // Ensure that all child views are at 1x scale, and visible, in case they were animating
306 // in.
307 mLayout.setVisibility(View.VISIBLE);
308 animationsForChildrenFromIndex(0 /* startIndex */, (index, animation) ->
309 animation.scaleX(1f).scaleY(1f).alpha(1f)).startAll();
310 }
311
312 @Override
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800313 Set<DynamicAnimation.ViewProperty> getAnimatedProperties() {
314 return Sets.newHashSet(
315 DynamicAnimation.TRANSLATION_X,
Joshua Tsuji1575e6b2019-01-30 13:43:28 -0500316 DynamicAnimation.TRANSLATION_Y,
317 DynamicAnimation.SCALE_X,
318 DynamicAnimation.SCALE_Y,
319 DynamicAnimation.ALPHA);
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800320 }
321
322 @Override
323 int getNextAnimationInChain(DynamicAnimation.ViewProperty property, int index) {
324 return NONE;
325 }
326
327 @Override
328 float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property) {
329 return 0;
330 }
331
332 @Override
333 SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view) {
334 return new SpringForce()
Joshua Tsuji010c2b12019-02-25 18:11:25 -0500335 .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
336 .setStiffness(SpringForce.STIFFNESS_LOW);
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800337 }
338
339 @Override
340 void onChildAdded(View child, int index) {
Joshua Tsujif49ee142019-05-29 16:32:01 -0400341 // If a bubble is added while the expand/collapse animations are playing, update the
342 // animation to include the new bubble.
343 if (mAnimatingExpand) {
344 startOrUpdateExpandAnimation();
345 } else if (mAnimatingCollapse) {
346 startOrUpdateCollapseAnimation();
347 } else {
Joshua Tsuji61b38f52019-05-31 16:20:22 -0400348 child.setTranslationX(getBubbleLeft(index));
Joshua Tsujif49ee142019-05-29 16:32:01 -0400349 animationForChild(child)
350 .translationY(
351 getExpandedY() - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR, /* from */
352 getExpandedY() /* to */)
353 .start();
354 updateBubblePositions();
355 }
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800356 }
357
358 @Override
Joshua Tsuji1575e6b2019-01-30 13:43:28 -0500359 void onChildRemoved(View child, int index, Runnable finishRemoval) {
Joshua Tsujic1108432019-02-22 16:10:12 -0500360 final PhysicsAnimationLayout.PhysicsPropertyAnimator animator = animationForChild(child);
Joshua Tsuji1575e6b2019-01-30 13:43:28 -0500361
Joshua Tsuji442b6272019-02-08 13:23:43 -0500362 // If we're removing the dragged-out bubble, that means it got dismissed.
363 if (child.equals(mBubbleDraggingOut)) {
Joshua Tsuji442b6272019-02-08 13:23:43 -0500364 mBubbleDraggingOut = null;
Joshua Tsuji4accf5982019-04-22 17:36:11 -0400365 finishRemoval.run();
Joshua Tsuji442b6272019-02-08 13:23:43 -0500366 } else {
Joshua Tsuji4accf5982019-04-22 17:36:11 -0400367 animator.alpha(0f, finishRemoval /* endAction */)
368 .withStiffness(SpringForce.STIFFNESS_HIGH)
369 .withDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY)
370 .scaleX(1.1f)
371 .scaleY(1.1f)
372 .start();
Joshua Tsuji1575e6b2019-01-30 13:43:28 -0500373 }
Joshua Tsuji442b6272019-02-08 13:23:43 -0500374
375 // Animate all the other bubbles to their new positions sans this bubble.
Lyn Han522e9ff2019-05-17 13:26:13 -0700376 updateBubblePositions();
Joshua Tsuji1575e6b2019-01-30 13:43:28 -0500377 }
378
379 @Override
Joshua Tsujif49ee142019-05-29 16:32:01 -0400380 void onChildReordered(View child, int oldIndex, int newIndex) {
381 updateBubblePositions();
Joshua Tsuji1575e6b2019-01-30 13:43:28 -0500382 }
383
Lyn Han522e9ff2019-05-17 13:26:13 -0700384 private void updateBubblePositions() {
Joshua Tsujif49ee142019-05-29 16:32:01 -0400385 if (mAnimatingExpand || mAnimatingCollapse) {
386 return;
387 }
388
Lyn Han522e9ff2019-05-17 13:26:13 -0700389 for (int i = 0; i < mLayout.getChildCount(); i++) {
Joshua Tsuji442b6272019-02-08 13:23:43 -0500390 final View bubble = mLayout.getChildAt(i);
391
392 // Don't animate the dragging out bubble, or it'll jump around while being dragged. It
393 // will be snapped to the correct X value after the drag (if it's not dismissed).
Lyn Han522e9ff2019-05-17 13:26:13 -0700394 if (bubble.equals(mBubbleDraggingOut)) {
395 return;
Joshua Tsuji442b6272019-02-08 13:23:43 -0500396 }
Joshua Tsujif49ee142019-05-29 16:32:01 -0400397
Lyn Han522e9ff2019-05-17 13:26:13 -0700398 animationForChild(bubble)
399 .translationX(getBubbleLeft(i))
400 .start();
Joshua Tsuji442b6272019-02-08 13:23:43 -0500401 }
402 }
403
Lyn Han522e9ff2019-05-17 13:26:13 -0700404 /**
405 * @param index Bubble index in row.
406 * @return Bubble left x from left edge of screen.
407 */
408 public float getBubbleLeft(int index) {
Lyn Han5390ce32019-05-31 07:25:50 -0700409 final float bubbleFromRowLeft = index * (mBubbleSizePx + getSpaceBetweenBubbles());
Lyn Han4a8efe32019-05-30 09:43:27 -0700410 return getRowLeft() + bubbleFromRowLeft;
Lyn Han522e9ff2019-05-17 13:26:13 -0700411 }
412
413 private float getRowLeft() {
414 if (mLayout == null) {
415 return 0;
416 }
Lyn Han4a8efe32019-05-30 09:43:27 -0700417
Lyn Han522e9ff2019-05-17 13:26:13 -0700418 int bubbleCount = mLayout.getChildCount();
Lyn Han522e9ff2019-05-17 13:26:13 -0700419
Lyn Han4a8efe32019-05-30 09:43:27 -0700420 final float totalBubbleWidth = bubbleCount * mBubbleSizePx;
421 final float totalGapWidth = (bubbleCount - 1) * getSpaceBetweenBubbles();
422 final float rowWidth = totalGapWidth + totalBubbleWidth;
Lyn Han522e9ff2019-05-17 13:26:13 -0700423
Lyn Han4a8efe32019-05-30 09:43:27 -0700424 final float centerScreen = mDisplaySize.x / 2f;
425 final float halfRow = rowWidth / 2f;
426 final float rowLeft = centerScreen - halfRow;
427
428 return rowLeft;
429 }
430
431 /**
432 * @return Space between bubbles in row above expanded view.
433 */
434 private float getSpaceBetweenBubbles() {
435 /**
436 * Ordered left to right:
437 * Screen edge
438 * [mExpandedViewPadding]
439 * Expanded view edge
440 * [launcherGridDiff] --- arbitrary value until launcher exports widths
441 * Launcher's app icon grid edge that we must match
442 */
Lyn Han1b4f25e2019-06-11 13:56:34 -0700443 final float rowMargins = (mExpandedViewPadding + mLauncherGridDiff) * 2;
Lyn Han4a8efe32019-05-30 09:43:27 -0700444 final float maxRowWidth = mDisplaySize.x - rowMargins;
445
446 final float totalBubbleWidth = mBubblesMaxRendered * mBubbleSizePx;
447 final float totalGapWidth = maxRowWidth - totalBubbleWidth;
448
449 final int gapCount = mBubblesMaxRendered - 1;
450 final float gapWidth = totalGapWidth / gapCount;
Lyn Han4a8efe32019-05-30 09:43:27 -0700451 return gapWidth;
Lyn Han522e9ff2019-05-17 13:26:13 -0700452 }
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800453}