Fixes positioning issues with the bubble stack.
- Waits for layout before moving the stack to the initial position, so that width/height are correct. This was not caught by tests because the TestablePhysicsAnimationLayout posts all of its controller dispatch methods to a Handler, which caused them to wait for layout only in the tests. This seems unavoidable, unfortunately. - Uses the status bar height resource value, rather than top inset, which is actually always zero. Test: atest SystemUITests Bug: 123635140 Change-Id: I2357445a7b1ab374e3a835884639e3b227207de3
This commit is contained in:
@@ -101,6 +101,7 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F
|
||||
private int mBubblePadding;
|
||||
private int mExpandedAnimateXDistance;
|
||||
private int mExpandedAnimateYDistance;
|
||||
private int mStatusBarHeight;
|
||||
|
||||
private boolean mIsExpanded;
|
||||
private int mExpandedBubbleHeight;
|
||||
@@ -153,6 +154,8 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F
|
||||
res.getDimensionPixelSize(R.dimen.bubble_expanded_animate_x_distance);
|
||||
mExpandedAnimateYDistance =
|
||||
res.getDimensionPixelSize(R.dimen.bubble_expanded_animate_y_distance);
|
||||
mStatusBarHeight =
|
||||
res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
|
||||
|
||||
mExpandedBubbleHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height);
|
||||
mDisplaySize = new Point();
|
||||
@@ -639,7 +642,7 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F
|
||||
if (getRootWindowInsets() != null) {
|
||||
WindowInsets insets = getRootWindowInsets();
|
||||
return Math.max(
|
||||
insets.getSystemWindowInsetTop(),
|
||||
mStatusBarHeight,
|
||||
insets.getDisplayCutout() != null
|
||||
? insets.getDisplayCutout().getSafeInsetTop()
|
||||
: 0);
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.systemui.bubbles.animation;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.PointF;
|
||||
import android.view.View;
|
||||
import android.view.WindowInsets;
|
||||
@@ -55,16 +56,19 @@ public class ExpandedAnimationController
|
||||
private float mBubblePaddingPx;
|
||||
/** Size of each bubble. */
|
||||
private float mBubbleSizePx;
|
||||
/** Height of the status bar. */
|
||||
private float mStatusBarHeight;
|
||||
|
||||
@Override
|
||||
protected void setLayout(PhysicsAnimationLayout layout) {
|
||||
super.setLayout(layout);
|
||||
mStackOffsetPx = layout.getResources().getDimensionPixelSize(
|
||||
R.dimen.bubble_stack_offset);
|
||||
mBubblePaddingPx = layout.getResources().getDimensionPixelSize(
|
||||
R.dimen.bubble_padding);
|
||||
mBubbleSizePx = layout.getResources().getDimensionPixelSize(
|
||||
R.dimen.individual_bubble_size);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,7 +107,7 @@ public class ExpandedAnimationController
|
||||
final WindowInsets insets = mLayout.getRootWindowInsets();
|
||||
if (insets != null) {
|
||||
return mBubblePaddingPx + Math.max(
|
||||
insets.getSystemWindowInsetTop(),
|
||||
mStatusBarHeight,
|
||||
insets.getDisplayCutout() != null
|
||||
? insets.getDisplayCutout().getSafeInsetTop()
|
||||
: 0);
|
||||
|
||||
@@ -16,15 +16,12 @@
|
||||
|
||||
package com.android.systemui.bubbles.animation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.RectF;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import androidx.dynamicanimation.animation.DynamicAnimation;
|
||||
import androidx.dynamicanimation.animation.FlingAnimation;
|
||||
@@ -88,9 +85,8 @@ public class StackAnimationController extends
|
||||
private int mBubbleOffscreen;
|
||||
/** How far down the screen the stack starts, when there is no pre-existing location. */
|
||||
private int mStackStartingVerticalOffset;
|
||||
|
||||
private Point mDisplaySize;
|
||||
private RectF mAllowableStackPositionRegion;
|
||||
/** Height of the status bar. */
|
||||
private float mStatusBarHeight;
|
||||
|
||||
@Override
|
||||
protected void setLayout(PhysicsAnimationLayout layout) {
|
||||
@@ -103,11 +99,8 @@ public class StackAnimationController extends
|
||||
mBubbleOffscreen = res.getDimensionPixelSize(R.dimen.bubble_stack_offscreen);
|
||||
mStackStartingVerticalOffset =
|
||||
res.getDimensionPixelSize(R.dimen.bubble_stack_starting_offset_y);
|
||||
|
||||
mDisplaySize = new Point();
|
||||
WindowManager wm =
|
||||
(WindowManager) layout.getContext().getSystemService(Context.WINDOW_SERVICE);
|
||||
wm.getDefaultDisplay().getSize(mDisplaySize);
|
||||
mStatusBarHeight =
|
||||
res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -203,10 +196,9 @@ public class StackAnimationController extends
|
||||
*/
|
||||
public RectF getAllowableStackPositionRegion() {
|
||||
final WindowInsets insets = mLayout.getRootWindowInsets();
|
||||
mAllowableStackPositionRegion = new RectF();
|
||||
|
||||
final RectF allowableRegion = new RectF();
|
||||
if (insets != null) {
|
||||
mAllowableStackPositionRegion.left =
|
||||
allowableRegion.left =
|
||||
-mBubbleOffscreen
|
||||
- mBubblePadding
|
||||
+ Math.max(
|
||||
@@ -214,7 +206,7 @@ public class StackAnimationController extends
|
||||
insets.getDisplayCutout() != null
|
||||
? insets.getDisplayCutout().getSafeInsetLeft()
|
||||
: 0);
|
||||
mAllowableStackPositionRegion.right =
|
||||
allowableRegion.right =
|
||||
mLayout.getWidth()
|
||||
- mIndividualBubbleSize
|
||||
+ mBubbleOffscreen
|
||||
@@ -222,17 +214,17 @@ public class StackAnimationController extends
|
||||
- Math.max(
|
||||
insets.getSystemWindowInsetRight(),
|
||||
insets.getDisplayCutout() != null
|
||||
? insets.getDisplayCutout().getSafeInsetRight()
|
||||
: 0);
|
||||
? insets.getDisplayCutout().getSafeInsetRight()
|
||||
: 0);
|
||||
|
||||
mAllowableStackPositionRegion.top =
|
||||
allowableRegion.top =
|
||||
mBubblePadding
|
||||
+ Math.max(
|
||||
insets.getSystemWindowInsetTop(),
|
||||
mStatusBarHeight,
|
||||
insets.getDisplayCutout() != null
|
||||
? insets.getDisplayCutout().getSafeInsetTop()
|
||||
: 0);
|
||||
mAllowableStackPositionRegion.bottom =
|
||||
? insets.getDisplayCutout().getSafeInsetTop()
|
||||
: 0);
|
||||
allowableRegion.bottom =
|
||||
mLayout.getHeight()
|
||||
- mIndividualBubbleSize
|
||||
- mBubblePadding
|
||||
@@ -243,7 +235,7 @@ public class StackAnimationController extends
|
||||
: 0);
|
||||
}
|
||||
|
||||
return mAllowableStackPositionRegion;
|
||||
return allowableRegion;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -287,31 +279,14 @@ public class StackAnimationController extends
|
||||
|
||||
@Override
|
||||
void onChildAdded(View child, int index) {
|
||||
// If this is the first child added, position the stack in its starting position.
|
||||
if (mLayout.getChildCount() == 1) {
|
||||
moveStackToStartPosition();
|
||||
}
|
||||
|
||||
if (mLayout.indexOfChild(child) == 0) {
|
||||
child.setTranslationY(mStackPosition.y);
|
||||
|
||||
// Pop in the new bubble.
|
||||
child.setScaleX(ANIMATE_IN_STARTING_SCALE);
|
||||
child.setScaleY(ANIMATE_IN_STARTING_SCALE);
|
||||
mLayout.animateValueForChildAtIndex(DynamicAnimation.SCALE_X, 0, 1f);
|
||||
mLayout.animateValueForChildAtIndex(DynamicAnimation.SCALE_Y, 0, 1f);
|
||||
|
||||
// Fade in the new bubble.
|
||||
child.setAlpha(0);
|
||||
mLayout.animateValueForChildAtIndex(DynamicAnimation.ALPHA, 0, 1f);
|
||||
|
||||
// Start the new bubble 4x the normal offset distance in the opposite direction. We'll
|
||||
// animate in from this position. Since the animations are chained, when the new bubble
|
||||
// flies in from the side, it will push the other ones out of the way.
|
||||
float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X);
|
||||
child.setTranslationX(mStackPosition.x - ANIMATE_TRANSLATION_FACTOR * xOffset);
|
||||
mLayout.animateValueForChildAtIndex(
|
||||
DynamicAnimation.TRANSLATION_X, 0, mStackPosition.x);
|
||||
// If this is the first child added, position the stack in its starting position before
|
||||
// animating in.
|
||||
moveStackToStartPosition(() -> animateInBubble(child));
|
||||
} else if (mLayout.indexOfChild(child) == 0) {
|
||||
// Otherwise, animate the bubble in if it's the newest bubble. If we're adding a bubble
|
||||
// to the back of the stack, it'll be largely invisible so don't bother animating it in.
|
||||
animateInBubble(child);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,10 +309,14 @@ public class StackAnimationController extends
|
||||
}
|
||||
|
||||
/** Moves the stack, without any animation, to the starting position. */
|
||||
private void moveStackToStartPosition() {
|
||||
mLayout.post(() -> setStackPosition(
|
||||
getAllowableStackPositionRegion().right,
|
||||
getAllowableStackPositionRegion().top + mStackStartingVerticalOffset));
|
||||
private void moveStackToStartPosition(Runnable after) {
|
||||
// Post to ensure that the layout's width and height have been calculated.
|
||||
mLayout.post(() -> {
|
||||
setStackPosition(
|
||||
getAllowableStackPositionRegion().right,
|
||||
getAllowableStackPositionRegion().top + mStackStartingVerticalOffset);
|
||||
after.run();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -379,6 +358,29 @@ public class StackAnimationController extends
|
||||
}
|
||||
}
|
||||
|
||||
/** Animates in the given bubble. */
|
||||
private void animateInBubble(View child) {
|
||||
child.setTranslationY(mStackPosition.y);
|
||||
|
||||
// Pop in the new bubble.
|
||||
child.setScaleX(ANIMATE_IN_STARTING_SCALE);
|
||||
child.setScaleY(ANIMATE_IN_STARTING_SCALE);
|
||||
mLayout.animateValueForChildAtIndex(DynamicAnimation.SCALE_X, 0, 1f);
|
||||
mLayout.animateValueForChildAtIndex(DynamicAnimation.SCALE_Y, 0, 1f);
|
||||
|
||||
// Fade in the new bubble.
|
||||
child.setAlpha(0);
|
||||
mLayout.animateValueForChildAtIndex(DynamicAnimation.ALPHA, 0, 1f);
|
||||
|
||||
// Start the new bubble 4x the normal offset distance in the opposite direction. We'll
|
||||
// animate in from this position. Since the animations are chained, when the new bubble
|
||||
// flies in from the side, it will push the other ones out of the way.
|
||||
float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X);
|
||||
child.setTranslationX(mStackPosition.x - ANIMATE_TRANSLATION_FACTOR * xOffset);
|
||||
mLayout.animateValueForChildAtIndex(
|
||||
DynamicAnimation.TRANSLATION_X, 0, mStackPosition.x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Springs the first bubble to the given final position, with the rest of the stack 'following'.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user