Adjust the flyout sizing and appearance to match the design spec.

Test: atest SystemUITests
Bug: 130234901
Change-Id: Ib7cb6ef58d27f7d296b2dd22a41749c5f3bfe67b
This commit is contained in:
Joshua Tsuji
2019-04-18 16:27:35 -04:00
parent b962712dd9
commit 36b1b2ce5d
5 changed files with 174 additions and 45 deletions

View File

@@ -14,7 +14,6 @@
~ limitations under the License
-->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<!-- TODO: Add the triangle pointing to the bubble stack. -->
<item>
<shape android:shape="rectangle">
<solid android:color="?android:attr/colorBackgroundFloating" />
@@ -22,8 +21,10 @@
android:bottomLeftRadius="?android:attr/dialogCornerRadius"
android:topLeftRadius="?android:attr/dialogCornerRadius"
android:bottomRightRadius="?android:attr/dialogCornerRadius"
android:topRightRadius="?android:attr/dialogCornerRadius"
/>
android:topRightRadius="?android:attr/dialogCornerRadius" />
<padding
android:left="@dimen/bubble_flyout_pointer_size"
android:right="@dimen/bubble_flyout_pointer_size" />
</shape>
</item>
</layer-list>

View File

@@ -15,19 +15,30 @@
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/bubble_flyout"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:background="@drawable/bubble_flyout"
android:padding="@dimen/bubble_flyout_padding"
android:translationZ="@dimen/bubble_flyout_elevation">
android:layout_height="wrap_content"
android:paddingLeft="@dimen/bubble_flyout_pointer_size"
android:paddingRight="@dimen/bubble_flyout_pointer_size">
<TextView
android:id="@+id/bubble_flyout_text"
android:layout_width="wrap_content"
<FrameLayout
android:id="@+id/bubble_flyout"
android:layout_height="wrap_content"
android:maxLines="2"
android:maxWidth="@dimen/bubble_flyout_maxwidth"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title" />
android:layout_width="wrap_content"
android:background="@drawable/bubble_flyout"
android:paddingLeft="@dimen/bubble_flyout_padding_x"
android:paddingRight="@dimen/bubble_flyout_padding_x"
android:paddingTop="@dimen/bubble_flyout_padding_y"
android:paddingBottom="@dimen/bubble_flyout_padding_y"
android:translationZ="@dimen/bubble_flyout_elevation">
<TextView
android:id="@+id/bubble_flyout_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@*android:string/config_bodyFontFamily"
android:maxLines="2"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2"/>
</FrameLayout>
</FrameLayout>

View File

@@ -1064,16 +1064,20 @@
<dimen name="bubble_elevation">1dp</dimen>
<!-- How much the bubble flyout text container is elevated. -->
<dimen name="bubble_flyout_elevation">4dp</dimen>
<!-- How much padding is around the flyout text. -->
<dimen name="bubble_flyout_padding">16dp</dimen>
<!-- The maximum width of a bubble flyout. -->
<dimen name="bubble_flyout_maxwidth">200dp</dimen>
<!-- How much padding is around the left and right sides of the flyout text. -->
<dimen name="bubble_flyout_padding_x">16dp</dimen>
<!-- How much padding is around the top and bottom of the flyout text. -->
<dimen name="bubble_flyout_padding_y">8dp</dimen>
<!-- Size of the triangle that points from the flyout to the bubble stack. -->
<dimen name="bubble_flyout_pointer_size">6dp</dimen>
<!-- How much space to leave between the flyout (tip of the arrow) and the bubble stack. -->
<dimen name="bubble_flyout_space_from_bubble">8dp</dimen>
<!-- Padding around a collapsed bubble -->
<dimen name="bubble_view_padding">0dp</dimen>
<!-- Padding between bubbles when displayed in expanded state -->
<dimen name="bubble_padding">8dp</dimen>
<!-- Size of individual bubbles. -->
<dimen name="individual_bubble_size">56dp</dimen>
<dimen name="individual_bubble_size">52dp</dimen>
<!-- How much to inset the icon in the circle -->
<dimen name="bubble_icon_inset">16dp</dimen>
<!-- Padding around the view displayed when the bubble is expanded -->

View File

@@ -22,16 +22,21 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.annotation.NonNull;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Outline;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.os.Bundle;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.util.StatsLog;
import android.view.Choreographer;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -56,6 +61,7 @@ import com.android.systemui.R;
import com.android.systemui.bubbles.animation.ExpandedAnimationController;
import com.android.systemui.bubbles.animation.PhysicsAnimationLayout;
import com.android.systemui.bubbles.animation.StackAnimationController;
import com.android.systemui.recents.TriangleShape;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import java.math.BigDecimal;
@@ -73,6 +79,9 @@ public class BubbleStackView extends FrameLayout {
/** Duration of the flyout alpha animations. */
private static final int FLYOUT_ALPHA_ANIMATION_DURATION = 100;
/** Max width of the flyout, in terms of percent of the screen width. */
private static final float FLYOUT_MAX_WIDTH_PERCENT = .6f;
/** How long to wait, in milliseconds, before hiding the flyout. */
@VisibleForTesting
static final int FLYOUT_HIDE_AFTER = 5000;
@@ -126,13 +135,17 @@ public class BubbleStackView extends FrameLayout {
private FrameLayout mExpandedViewContainer;
private View mFlyout;
private FrameLayout mFlyoutContainer;
private FrameLayout mFlyout;
private TextView mFlyoutText;
private ShapeDrawable mLeftFlyoutTriangle;
private ShapeDrawable mRightFlyoutTriangle;
/** Spring animation for the flyout. */
private SpringAnimation mFlyoutSpring;
/** Runnable that fades out the flyout and then sets it to GONE. */
private Runnable mHideFlyout =
() -> mFlyout.animate().alpha(0f).withEndAction(() -> mFlyout.setVisibility(GONE));
() -> mFlyoutContainer.animate().alpha(0f).withEndAction(
() -> mFlyoutContainer.setVisibility(GONE));
/** Layout change listener that moves the stack to the nearest valid position on rotation. */
private OnLayoutChangeListener mMoveStackToValidPositionOnLayoutListener;
@@ -146,6 +159,9 @@ public class BubbleStackView extends FrameLayout {
private int mBubbleSize;
private int mBubblePadding;
private int mFlyoutPadding;
private int mFlyoutSpaceFromBubble;
private int mPointerSize;
private int mExpandedAnimateXDistance;
private int mExpandedAnimateYDistance;
private int mStatusBarHeight;
@@ -218,6 +234,9 @@ public class BubbleStackView extends FrameLayout {
Resources res = getResources();
mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding);
mFlyoutPadding = res.getDimensionPixelSize(R.dimen.bubble_flyout_padding_x);
mFlyoutSpaceFromBubble = res.getDimensionPixelSize(R.dimen.bubble_flyout_space_from_bubble);
mPointerSize = res.getDimensionPixelSize(R.dimen.bubble_flyout_pointer_size);
mExpandedAnimateXDistance =
res.getDimensionPixelSize(R.dimen.bubble_expanded_animate_x_distance);
mExpandedAnimateYDistance =
@@ -244,7 +263,6 @@ public class BubbleStackView extends FrameLayout {
getResources().getInteger(R.integer.bubbles_max_rendered));
mBubbleContainer.setController(mStackAnimationController);
mBubbleContainer.setElevation(elevation);
mBubbleContainer.setPadding(padding, 0, padding, 0);
mBubbleContainer.setClipChildren(false);
addView(mBubbleContainer, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
@@ -254,16 +272,17 @@ public class BubbleStackView extends FrameLayout {
mExpandedViewContainer.setClipChildren(false);
addView(mExpandedViewContainer);
mFlyout = mInflater.inflate(R.layout.bubble_flyout, this, false);
mFlyout.setVisibility(GONE);
mFlyout.animate()
mFlyoutContainer = (FrameLayout) mInflater.inflate(R.layout.bubble_flyout, this, false);
mFlyoutContainer.setVisibility(GONE);
mFlyoutContainer.setClipToPadding(false);
mFlyoutContainer.setClipChildren(false);
mFlyoutContainer.animate()
.setDuration(FLYOUT_ALPHA_ANIMATION_DURATION)
.setInterpolator(new AccelerateDecelerateInterpolator());
addView(mFlyout);
mFlyoutText = mFlyout.findViewById(R.id.bubble_flyout_text);
mFlyoutSpring = new SpringAnimation(mFlyout, DynamicAnimation.TRANSLATION_X);
mFlyout = mFlyoutContainer.findViewById(R.id.bubble_flyout);
addView(mFlyoutContainer);
setupFlyout();
mExpandedViewXAnim =
new SpringAnimation(mExpandedViewContainer, DynamicAnimation.TRANSLATION_X);
@@ -592,7 +611,7 @@ public class BubbleStackView extends FrameLayout {
}
// Outside parts of view we care about.
return null;
} else if (isIntersecting(mFlyout, x, y)) {
} else if (mFlyoutContainer.getVisibility() == VISIBLE && isIntersecting(mFlyout, x, y)) {
return mFlyout;
}
@@ -828,27 +847,52 @@ public class BubbleStackView extends FrameLayout {
if (updateMessage != null && !isExpanded() && !mIsExpansionAnimating && !mIsDragging) {
final PointF stackPos = mStackAnimationController.getStackPosition();
mFlyout.setAlpha(0f);
mFlyout.setVisibility(VISIBLE);
// Set the flyout TextView's max width in terms of percent, and then subtract out the
// padding so that the entire flyout view will be the desired width (rather than the
// TextView being the desired width + extra padding).
mFlyoutText.setMaxWidth(
(int) (getWidth() * FLYOUT_MAX_WIDTH_PERCENT) - mFlyoutPadding * 2);
mFlyoutContainer.setAlpha(0f);
mFlyoutContainer.setVisibility(VISIBLE);
mFlyoutText.setText(updateMessage);
mFlyout.measure(WRAP_CONTENT, WRAP_CONTENT);
post(() -> {
final boolean onLeft = mStackAnimationController.isStackOnLeftSide();
final boolean onLeft = mStackAnimationController.isStackOnLeftSide();
if (onLeft) {
mLeftFlyoutTriangle.setAlpha(255);
mRightFlyoutTriangle.setAlpha(0);
} else {
mLeftFlyoutTriangle.setAlpha(0);
mRightFlyoutTriangle.setAlpha(255);
}
mFlyoutContainer.post(() -> {
// Multi line flyouts get top-aligned to the bubble.
if (mFlyoutText.getLineCount() > 1) {
mFlyoutContainer.setTranslationY(stackPos.y);
} else {
// Single line flyouts are vertically centered with respect to the bubble.
mFlyoutContainer.setTranslationY(
stackPos.y + (mBubbleSize - mFlyout.getHeight()) / 2f);
}
final float destinationX = onLeft
? stackPos.x + mBubbleSize + mBubblePadding
: stackPos.x - mFlyout.getMeasuredWidth();
? stackPos.x + mBubbleSize + mFlyoutSpaceFromBubble
: stackPos.x - mFlyoutContainer.getWidth() - mFlyoutSpaceFromBubble;
// Translate towards the stack slightly, then spring out from the stack.
mFlyout.setTranslationX(destinationX + (onLeft ? -mBubblePadding : mBubblePadding));
mFlyout.setTranslationY(stackPos.y);
mFlyoutContainer.setTranslationX(
destinationX + (onLeft ? -mBubblePadding : mBubblePadding));
mFlyout.animate().alpha(1f);
mFlyoutContainer.animate().alpha(1f);
mFlyoutSpring.animateToFinalPosition(destinationX);
mFlyout.removeCallbacks(mHideFlyout);
mFlyout.postDelayed(mHideFlyout, FLYOUT_HIDE_AFTER);
});
logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__FLYOUT);
}
}
@@ -869,7 +913,7 @@ public class BubbleStackView extends FrameLayout {
mBubbleContainer.getBoundsOnScreen(outRect);
}
if (mFlyout.getVisibility() == View.VISIBLE) {
if (mFlyoutContainer.getVisibility() == View.VISIBLE) {
final Rect flyoutBounds = new Rect();
mFlyout.getBoundsOnScreen(flyoutBounds);
outRect.union(flyoutBounds);
@@ -923,6 +967,74 @@ public class BubbleStackView extends FrameLayout {
}
}
/** Sets up the flyout views and drawables. */
private void setupFlyout() {
// Retrieve the styled floating background color.
TypedArray ta = mContext.obtainStyledAttributes(
new int[] {android.R.attr.colorBackgroundFloating});
final int floatingBackgroundColor = ta.getColor(0, Color.WHITE);
ta.recycle();
// Retrieve the flyout background, which is currently a rounded white rectangle with a
// shadow but no triangular arrow pointing anywhere.
final LayerDrawable flyoutBackground = (LayerDrawable) mFlyout.getBackground();
// Create the triangle drawables and set their color.
mLeftFlyoutTriangle =
new ShapeDrawable(TriangleShape.createHorizontal(
mPointerSize, mPointerSize, true /* isPointingLeft */));
mRightFlyoutTriangle =
new ShapeDrawable(TriangleShape.createHorizontal(
mPointerSize, mPointerSize, false /* isPointingLeft */));
mLeftFlyoutTriangle.getPaint().setColor(floatingBackgroundColor);
mRightFlyoutTriangle.getPaint().setColor(floatingBackgroundColor);
// Add both triangles to the drawable. We'll show and hide the appropriate ones when we show
// the flyout.
final int leftTriangleIndex = flyoutBackground.addLayer(mLeftFlyoutTriangle);
flyoutBackground.setLayerSize(leftTriangleIndex, mPointerSize, mPointerSize);
flyoutBackground.setLayerGravity(leftTriangleIndex, Gravity.LEFT | Gravity.CENTER_VERTICAL);
flyoutBackground.setLayerInsetLeft(leftTriangleIndex, -mPointerSize);
final int rightTriangleIndex = flyoutBackground.addLayer(mRightFlyoutTriangle);
flyoutBackground.setLayerSize(rightTriangleIndex, mPointerSize, mPointerSize);
flyoutBackground.setLayerGravity(
rightTriangleIndex, Gravity.RIGHT | Gravity.CENTER_VERTICAL);
flyoutBackground.setLayerInsetRight(rightTriangleIndex, -mPointerSize);
// Append the appropriate triangle's outline to the view's outline so that the shadows look
// correct.
mFlyout.setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
final boolean leftPointing = mStackAnimationController.isStackOnLeftSide();
// Get the outline from the appropriate triangle.
final Outline triangleOutline = new Outline();
if (leftPointing) {
mLeftFlyoutTriangle.getOutline(triangleOutline);
} else {
mRightFlyoutTriangle.getOutline(triangleOutline);
}
// Offset it to the correct position, since it has no intrinsic position since
// that is maintained by the parent LayerDrawable.
triangleOutline.offset(
leftPointing ? -mPointerSize : mFlyout.getWidth(),
mFlyout.getHeight() / 2 - mPointerSize / 2);
// Merge the outlines.
final Outline compoundOutline = new Outline();
flyoutBackground.getOutline(compoundOutline);
compoundOutline.mPath.addPath(triangleOutline.mPath);
outline.set(compoundOutline);
}
});
mFlyoutText = mFlyout.findViewById(R.id.bubble_flyout_text);
mFlyoutSpring = new SpringAnimation(mFlyoutContainer, DynamicAnimation.TRANSLATION_X);
}
private void applyCurrentState() {
Log.d(TAG, "applyCurrentState: mIsExpanded=" + mIsExpanded);
mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE);

View File

@@ -120,7 +120,11 @@ public class StackAnimationController extends
private float mStackOffset;
/** Diameter of the bubbles themselves. */
private int mIndividualBubbleSize;
/** Size of spacing around the bubbles, separating it from the edge of the screen. */
/**
* The amount of space to add between the bubbles and certain UI elements, such as the top of
* the screen or the IME. This does not apply to the left/right sides of the screen since the
* stack goes offscreen intentionally.
*/
private int mBubblePadding;
/** How far offscreen the stack rests. */
private int mBubbleOffscreen;
@@ -381,7 +385,6 @@ public class StackAnimationController extends
if (insets != null) {
allowableRegion.left =
-mBubbleOffscreen
- mBubblePadding
+ Math.max(
insets.getSystemWindowInsetLeft(),
insets.getDisplayCutout() != null
@@ -391,7 +394,6 @@ public class StackAnimationController extends
mLayout.getWidth()
- mIndividualBubbleSize
+ mBubbleOffscreen
- mBubblePadding
- Math.max(
insets.getSystemWindowInsetRight(),
insets.getDisplayCutout() != null
@@ -521,7 +523,6 @@ public class StackAnimationController extends
if (mLayout.getChildCount() > 0) {
property.setValue(mLayout.getChildAt(0), value);
if (mLayout.getChildCount() > 1) {
animationForChildAtIndex(1)
.property(property, value + getOffsetForChainedPropertyAnimation(property))