Add shadows back to bubble avatar
* Add shadows back -- icon lib is no longer adding shadows but
bubbles should still have them for background protection.
* The shadow is active on all bubbles when:
- the stack is being dragged around & bubbles trail behind it
- the stack is expanded (the bubbles can be dragged to dismiss &
can show on top of the expanded view or overflow)
* When the stack is resting, only the top two bubbles show a shadow
& the bubbles below that animate their Z translation so the change
is smooth.
* Also put the # of bubbles we show in the stack in a static int &
updated usages
Test: visual - add 5 of the 'test bubbles' from the test app
and notice the white backgrounds don't blend together
due to the shadow & that they are distinct when
placed on a white background
- drag / fling the stack around & notice shadows show
for all the bubbles while moving but once they come
to rest you don't see a stack of shadows
Bug: 183657577
Change-Id: I0c273f4d0261db9eac1b49ef584fd62fbb67cc9a
This commit is contained in:
@@ -23,15 +23,19 @@ import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PaintFlagsDrawFilter;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.PathParser;
|
||||
import android.view.View;
|
||||
import android.view.ViewOutlineProvider;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.android.launcher3.icons.DotRenderer;
|
||||
import com.android.launcher3.icons.IconNormalizer;
|
||||
import com.android.wm.shell.animation.Interpolators;
|
||||
|
||||
import java.util.EnumSet;
|
||||
@@ -104,6 +108,19 @@ public class BadgedImageView extends ImageView {
|
||||
|
||||
setFocusable(true);
|
||||
setClickable(true);
|
||||
setOutlineProvider(new ViewOutlineProvider() {
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline) {
|
||||
BadgedImageView.this.getOutline(outline);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void getOutline(Outline outline) {
|
||||
final int bubbleSize = mPositioner.getBubbleSize();
|
||||
final int normalizedSize = IconNormalizer.getNormalizedCircleSize(bubbleSize);
|
||||
final int inset = (bubbleSize - normalizedSize) / 2;
|
||||
outline.setOval(inset, inset, inset + normalizedSize, inset + normalizedSize);
|
||||
}
|
||||
|
||||
public void initialize(BubblePositioner positioner) {
|
||||
|
||||
@@ -56,11 +56,8 @@ public class BubblePositioner {
|
||||
public static final int TASKBAR_POSITION_LEFT = 1;
|
||||
public static final int TASKBAR_POSITION_BOTTOM = 2;
|
||||
|
||||
/**
|
||||
* The bitmap in the bubble is slightly smaller than the overall size of the bubble.
|
||||
* This is the percentage to scale the image down based on the overall bubble size.
|
||||
*/
|
||||
private static final float BUBBLE_BITMAP_SIZE_PERCENT = 0.86f;
|
||||
/** 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;
|
||||
|
||||
private Context mContext;
|
||||
private WindowManager mWindowManager;
|
||||
|
||||
@@ -22,6 +22,7 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW;
|
||||
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.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
@@ -632,6 +633,7 @@ public class BubbleStackView extends FrameLayout
|
||||
// First, see if the magnetized object consumes the event - if so, we shouldn't move the
|
||||
// bubble since it's stuck to the target.
|
||||
if (!passEventToMagnetizedObject(ev)) {
|
||||
updateBubbleShadows(true /* showForAllBubbles */);
|
||||
if (mBubbleData.isExpanded() || mPositioner.showingInTaskbar()) {
|
||||
mExpandedAnimationController.dragBubbleOut(
|
||||
v, viewInitialX + dx, viewInitialY + dy);
|
||||
@@ -671,7 +673,7 @@ public class BubbleStackView extends FrameLayout
|
||||
mStackAnimationController.flingStackThenSpringToEdge(
|
||||
viewInitialX + dx, velX, velY) <= 0;
|
||||
final boolean updateForCollapsedStack = oldOnLeft != mStackOnLeftOrWillBe;
|
||||
updateBadgesAndZOrder(updateForCollapsedStack);
|
||||
updateBadges(updateForCollapsedStack);
|
||||
logBubbleEvent(null /* no bubble associated with bubble stack move */,
|
||||
FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__STACK_MOVED);
|
||||
}
|
||||
@@ -783,9 +785,9 @@ public class BubbleStackView extends FrameLayout
|
||||
mBubbleController.onAllBubblesAnimatedOut();
|
||||
}
|
||||
};
|
||||
|
||||
mStackAnimationController = new StackAnimationController(
|
||||
floatingContentCoordinator, this::getBubbleCount, onBubbleAnimatedOut, mPositioner);
|
||||
floatingContentCoordinator, this::getBubbleCount, onBubbleAnimatedOut,
|
||||
this::animateShadows /* onStackAnimationFinished */, mPositioner);
|
||||
|
||||
mExpandedAnimationController = new ExpandedAnimationController(
|
||||
mPositioner, mExpandedViewPadding, onBubbleAnimatedOut);
|
||||
@@ -1505,13 +1507,12 @@ public class BubbleStackView extends FrameLayout
|
||||
// Set the dot position to the opposite of the side the stack is resting on, since the stack
|
||||
// resting slightly off-screen would result in the dot also being off-screen.
|
||||
bubble.getIconView().setDotBadgeOnLeft(!mStackOnLeftOrWillBe /* onLeft */);
|
||||
|
||||
bubble.getIconView().setOnClickListener(mBubbleClickListener);
|
||||
bubble.getIconView().setOnTouchListener(mBubbleTouchListener);
|
||||
|
||||
mBubbleContainer.addView(bubble.getIconView(), 0,
|
||||
new FrameLayout.LayoutParams(mPositioner.getBubbleSize(),
|
||||
mPositioner.getBubbleSize()));
|
||||
updateBubbleShadows(false /* showForAllBubbles */);
|
||||
animateInFlyoutForBubble(bubble);
|
||||
requestUpdate();
|
||||
logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__POSTED);
|
||||
@@ -1566,7 +1567,8 @@ public class BubbleStackView extends FrameLayout
|
||||
};
|
||||
if (mIsExpanded || isExpansionAnimating()) {
|
||||
reorder.run();
|
||||
updateBadgesAndZOrder(false /* setBadgeForCollapsedStack */);
|
||||
updateBadges(false /* setBadgeForCollapsedStack */);
|
||||
updateZOrder();
|
||||
} else if (!isExpansionAnimating()) {
|
||||
List<View> bubbleViews = bubbles.stream()
|
||||
.map(b -> b.getIconView()).collect(Collectors.toList());
|
||||
@@ -1803,7 +1805,8 @@ public class BubbleStackView extends FrameLayout
|
||||
}
|
||||
beforeExpandedViewAnimation();
|
||||
|
||||
updateBadgesAndZOrder(false /* setBadgeForCollapsedStack */);
|
||||
updateZOrder();
|
||||
updateBadges(false /* setBadgeForCollapsedStack */);
|
||||
mBubbleContainer.setActiveController(mExpandedAnimationController);
|
||||
updateOverflowVisibility();
|
||||
updatePointerPosition();
|
||||
@@ -2014,7 +2017,8 @@ public class BubbleStackView extends FrameLayout
|
||||
mExpandedBubble));
|
||||
}
|
||||
updateOverflowVisibility();
|
||||
updateBadgesAndZOrder(true /* setBadgeForCollapsedStack */);
|
||||
updateZOrder();
|
||||
updateBadges(true /* setBadgeForCollapsedStack */);
|
||||
afterExpandedViewAnimation();
|
||||
if (previouslySelected != null) {
|
||||
previouslySelected.setTaskViewVisibility(false);
|
||||
@@ -2714,14 +2718,56 @@ public class BubbleStackView extends FrameLayout
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the appropriate Z-order, badge, and dot position for each bubble in the stack.
|
||||
* Animate dot and badge changes.
|
||||
* Updates whether each of the bubbles should show shadows. When collapsed & resting, only the
|
||||
* visible bubbles (top 2) will show a shadow. When the stack is being dragged, everything
|
||||
* shows a shadow. When an individual bubble is dragged out, it should show a shadow.
|
||||
*/
|
||||
private void updateBadgesAndZOrder(boolean setBadgeForCollapsedStack) {
|
||||
private void updateBubbleShadows(boolean showForAllBubbles) {
|
||||
int bubbleCount = getBubbleCount();
|
||||
for (int i = 0; i < bubbleCount; i++) {
|
||||
final float z = (mMaxBubbles * mBubbleElevation) - i;
|
||||
BadgedImageView bv = (BadgedImageView) mBubbleContainer.getChildAt(i);
|
||||
boolean isDraggedOut = mMagnetizedObject != null
|
||||
&& mMagnetizedObject.getUnderlyingObject().equals(bv);
|
||||
if (showForAllBubbles || isDraggedOut) {
|
||||
bv.setZ(z);
|
||||
} else {
|
||||
final float tz = i < NUM_VISIBLE_WHEN_RESTING ? z : 0f;
|
||||
bv.setZ(tz);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When the bubbles are flung and then rest, the shadows stack up for the bubbles hidden
|
||||
* beneath the top two bubbles, to avoid this we animate the Z translations once the stack
|
||||
* is resting so that they fade away nicely.
|
||||
*/
|
||||
private void animateShadows() {
|
||||
int bubbleCount = getBubbleCount();
|
||||
for (int i = 0; i < bubbleCount; i++) {
|
||||
BadgedImageView bv = (BadgedImageView) mBubbleContainer.getChildAt(i);
|
||||
boolean fullShadow = i < NUM_VISIBLE_WHEN_RESTING;
|
||||
if (!fullShadow) {
|
||||
bv.animate().translationZ(0).start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateZOrder() {
|
||||
int bubbleCount = getBubbleCount();
|
||||
for (int i = 0; i < bubbleCount; i++) {
|
||||
BadgedImageView bv = (BadgedImageView) mBubbleContainer.getChildAt(i);
|
||||
bv.setZ(i < NUM_VISIBLE_WHEN_RESTING
|
||||
? (mMaxBubbles * mBubbleElevation) - i
|
||||
: 0f);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateBadges(boolean setBadgeForCollapsedStack) {
|
||||
int bubbleCount = getBubbleCount();
|
||||
for (int i = 0; i < bubbleCount; i++) {
|
||||
BadgedImageView bv = (BadgedImageView) mBubbleContainer.getChildAt(i);
|
||||
bv.setZ((mMaxBubbles * mBubbleElevation) - i);
|
||||
if (mIsExpanded) {
|
||||
// If we're not displaying vertically, we always show the badge on the left.
|
||||
boolean onLeft = mPositioner.showBubblesVertically() && !mStackOnLeftOrWillBe;
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.wm.shell.bubbles.animation;
|
||||
|
||||
import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PointF;
|
||||
@@ -289,7 +291,8 @@ public class ExpandedAnimationController
|
||||
path.lineTo(stackedX, expandedY);
|
||||
|
||||
// Then, draw a line down to the stack position.
|
||||
path.lineTo(stackedX, mCollapsePoint.y + Math.min(index, 1) * mStackOffsetPx);
|
||||
path.lineTo(stackedX, mCollapsePoint.y
|
||||
+ Math.min(index, NUM_VISIBLE_WHEN_RESTING - 1) * mStackOffsetPx);
|
||||
}
|
||||
|
||||
// The lead bubble should be the bubble with the longest distance to travel when we're
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.wm.shell.bubbles.animation;
|
||||
|
||||
import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.PointF;
|
||||
@@ -255,14 +257,21 @@ public class StackAnimationController extends
|
||||
*/
|
||||
private Runnable mOnBubbleAnimatedOutAction;
|
||||
|
||||
/**
|
||||
* Callback to run whenever the stack is finished being flung somewhere.
|
||||
*/
|
||||
private Runnable mOnStackAnimationFinished;
|
||||
|
||||
public StackAnimationController(
|
||||
FloatingContentCoordinator floatingContentCoordinator,
|
||||
IntSupplier bubbleCountSupplier,
|
||||
Runnable onBubbleAnimatedOutAction,
|
||||
Runnable onStackAnimationFinished,
|
||||
BubblePositioner positioner) {
|
||||
mFloatingContentCoordinator = floatingContentCoordinator;
|
||||
mBubbleCountSupplier = bubbleCountSupplier;
|
||||
mOnBubbleAnimatedOutAction = onBubbleAnimatedOutAction;
|
||||
mOnStackAnimationFinished = onStackAnimationFinished;
|
||||
mPositioner = positioner;
|
||||
}
|
||||
|
||||
@@ -672,6 +681,10 @@ public class StackAnimationController extends
|
||||
mPositioner.setRestingPosition(mStackPosition);
|
||||
}
|
||||
|
||||
if (mOnStackAnimationFinished != null) {
|
||||
mOnStackAnimationFinished.run();
|
||||
}
|
||||
|
||||
if (after != null) {
|
||||
for (Runnable callback : after) {
|
||||
callback.run();
|
||||
@@ -716,7 +729,7 @@ public class StackAnimationController extends
|
||||
} else {
|
||||
// We only show the first two bubbles in the stack & the rest hide behind them
|
||||
// so they don't need an offset.
|
||||
return index > 1 ? 0f : mStackOffset;
|
||||
return index > (NUM_VISIBLE_WHEN_RESTING - 1) ? 0f : mStackOffset;
|
||||
}
|
||||
} else {
|
||||
return 0f;
|
||||
@@ -825,7 +838,8 @@ public class StackAnimationController extends
|
||||
private void moveToFinalIndex(View view, int newIndex,
|
||||
Runnable finishReorder) {
|
||||
final ViewPropertyAnimator animator = view.animate()
|
||||
.translationY(getStackPosition().y + Math.min(newIndex, 1) * mStackOffset)
|
||||
.translationY(getStackPosition().y
|
||||
+ Math.min(newIndex, NUM_VISIBLE_WHEN_RESTING - 1) * mStackOffset)
|
||||
.setDuration(BUBBLE_SWAP_DURATION)
|
||||
.withEndAction(() -> {
|
||||
view.setTag(R.id.reorder_animator_tag, null);
|
||||
@@ -834,8 +848,9 @@ public class StackAnimationController extends
|
||||
view.setTag(R.id.reorder_animator_tag, animator);
|
||||
}
|
||||
|
||||
// TODO: do we need this & BubbleStackView#updateBadgesAndZOrder?
|
||||
private void updateBadgesAndZOrder(View v, int index) {
|
||||
v.setZ((mMaxBubbles * mElevation) - index);
|
||||
v.setZ(index < NUM_VISIBLE_WHEN_RESTING ? (mMaxBubbles * mElevation) - index : 0f);
|
||||
BadgedImageView bv = (BadgedImageView) v;
|
||||
if (index == 0) {
|
||||
bv.showDotAndBadge(!isStackOnLeftSide());
|
||||
@@ -939,8 +954,9 @@ public class StackAnimationController extends
|
||||
final float yOffset = getOffsetForChainedPropertyAnimation(
|
||||
DynamicAnimation.TRANSLATION_Y, 0);
|
||||
for (int i = 0; i < mLayout.getChildCount(); i++) {
|
||||
mLayout.getChildAt(i).setTranslationX(pos.x + (Math.min(i, 1) * xOffset));
|
||||
mLayout.getChildAt(i).setTranslationY(pos.y + (Math.min(i, 1) * yOffset));
|
||||
float index = Math.min(i, NUM_VISIBLE_WHEN_RESTING - 1);
|
||||
mLayout.getChildAt(i).setTranslationX(pos.x + (index * xOffset));
|
||||
mLayout.getChildAt(i).setTranslationY(pos.y + (index * yOffset));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ public class StackAnimationControllerTest extends PhysicsAnimationLayoutTestCase
|
||||
public int getAsInt() {
|
||||
return mLayout.getChildCount();
|
||||
}
|
||||
}, mock(Runnable.class)));
|
||||
}, mock(Runnable.class), mock(Runnable.class)));
|
||||
mLayout.setActiveController(mStackController);
|
||||
addOneMoreThanBubbleLimitBubbles();
|
||||
mStackOffset = mLayout.getResources().getDimensionPixelSize(R.dimen.bubble_stack_offset);
|
||||
@@ -307,10 +307,12 @@ public class StackAnimationControllerTest extends PhysicsAnimationLayoutTestCase
|
||||
TestableStackController(
|
||||
FloatingContentCoordinator floatingContentCoordinator,
|
||||
IntSupplier bubbleCountSupplier,
|
||||
Runnable onBubbleAnimatedOutAction) {
|
||||
Runnable onBubbleAnimatedOutAction,
|
||||
Runnable onStackAnimationFinished) {
|
||||
super(floatingContentCoordinator,
|
||||
bubbleCountSupplier,
|
||||
onBubbleAnimatedOutAction,
|
||||
onStackAnimationFinished,
|
||||
new TestableBubblePositioner(mContext, mock(WindowManager.class)));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user