Merge "Assorted dot-wrangling." into rvc-dev am: eb7da42d27 am: 3c39fd60a0
Change-Id: Ieb82b88faf8ad2de6af97c38640ee95b4be12281
This commit is contained in:
@@ -28,6 +28,8 @@ import com.android.launcher3.icons.DotRenderer;
|
||||
import com.android.systemui.Interpolators;
|
||||
import com.android.systemui.R;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
/**
|
||||
* View that displays an adaptive icon with an app-badge and a dot.
|
||||
*
|
||||
@@ -42,12 +44,27 @@ public class BadgedImageView extends ImageView {
|
||||
/** Same as value in Launcher3 IconShape */
|
||||
public static final int DEFAULT_PATH_SIZE = 100;
|
||||
|
||||
static final int DOT_STATE_DEFAULT = 0;
|
||||
static final int DOT_STATE_SUPPRESSED_FOR_FLYOUT = 1;
|
||||
static final int DOT_STATE_ANIMATING = 2;
|
||||
/**
|
||||
* Flags that suppress the visibility of the 'new' dot, for one reason or another. If any of
|
||||
* these flags are set, the dot will not be shown even if {@link Bubble#showDot()} returns true.
|
||||
*/
|
||||
enum SuppressionFlag {
|
||||
// Suppressed because the flyout is visible - it will morph into the dot via animation.
|
||||
FLYOUT_VISIBLE,
|
||||
// Suppressed because this bubble is behind others in the collapsed stack.
|
||||
BEHIND_STACK,
|
||||
}
|
||||
|
||||
// Flyout gets shown before the dot
|
||||
private int mCurrentDotState = DOT_STATE_SUPPRESSED_FOR_FLYOUT;
|
||||
/**
|
||||
* Start by suppressing the dot because the flyout is visible - most bubbles are added with a
|
||||
* flyout, so this is a reasonable default.
|
||||
*/
|
||||
private final EnumSet<SuppressionFlag> mDotSuppressionFlags =
|
||||
EnumSet.of(SuppressionFlag.FLYOUT_VISIBLE);
|
||||
|
||||
private float mDotScale = 0f;
|
||||
private float mAnimatingToDotScale = 0f;
|
||||
private boolean mDotIsAnimating = false;
|
||||
|
||||
private BubbleViewProvider mBubble;
|
||||
|
||||
@@ -57,8 +74,6 @@ public class BadgedImageView extends ImageView {
|
||||
private boolean mOnLeft;
|
||||
|
||||
private int mDotColor;
|
||||
private float mDotScale = 0f;
|
||||
private boolean mDotDrawn;
|
||||
|
||||
private Rect mTempBounds = new Rect();
|
||||
|
||||
@@ -88,23 +103,21 @@ public class BadgedImageView extends ImageView {
|
||||
/**
|
||||
* Updates the view with provided info.
|
||||
*/
|
||||
public void update(BubbleViewProvider bubble) {
|
||||
public void setRenderedBubble(BubbleViewProvider bubble) {
|
||||
mBubble = bubble;
|
||||
setImageBitmap(bubble.getBadgedImage());
|
||||
setDotState(DOT_STATE_SUPPRESSED_FOR_FLYOUT);
|
||||
mDotColor = bubble.getDotColor();
|
||||
drawDot(bubble.getDotPath());
|
||||
animateDot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
if (isDotHidden()) {
|
||||
mDotDrawn = false;
|
||||
|
||||
if (!shouldDrawDot()) {
|
||||
return;
|
||||
}
|
||||
mDotDrawn = mDotScale > 0.1f;
|
||||
|
||||
getDrawingRect(mTempBounds);
|
||||
|
||||
mDrawParams.color = mDotColor;
|
||||
@@ -115,23 +128,33 @@ public class BadgedImageView extends ImageView {
|
||||
mDotRenderer.draw(canvas, mDrawParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the dot state, does not animate changes.
|
||||
*/
|
||||
void setDotState(int state) {
|
||||
mCurrentDotState = state;
|
||||
if (state == DOT_STATE_SUPPRESSED_FOR_FLYOUT || state == DOT_STATE_DEFAULT) {
|
||||
mDotScale = mBubble.showDot() ? 1f : 0f;
|
||||
invalidate();
|
||||
/** Adds a dot suppression flag, updating dot visibility if needed. */
|
||||
void addDotSuppressionFlag(SuppressionFlag flag) {
|
||||
if (mDotSuppressionFlags.add(flag)) {
|
||||
// Update dot visibility, and animate out if we're now behind the stack.
|
||||
updateDotVisibility(flag == SuppressionFlag.BEHIND_STACK /* animate */);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the dot should be hidden based on current dot state.
|
||||
*/
|
||||
private boolean isDotHidden() {
|
||||
return (mCurrentDotState == DOT_STATE_DEFAULT && !mBubble.showDot())
|
||||
|| mCurrentDotState == DOT_STATE_SUPPRESSED_FOR_FLYOUT;
|
||||
/** Removes a dot suppression flag, updating dot visibility if needed. */
|
||||
void removeDotSuppressionFlag(SuppressionFlag flag) {
|
||||
if (mDotSuppressionFlags.remove(flag)) {
|
||||
// Update dot visibility, animating if we're no longer behind the stack.
|
||||
updateDotVisibility(flag == SuppressionFlag.BEHIND_STACK);
|
||||
}
|
||||
}
|
||||
|
||||
/** Updates the visibility of the dot, animating if requested. */
|
||||
void updateDotVisibility(boolean animate) {
|
||||
final float targetScale = shouldDrawDot() ? 1f : 0f;
|
||||
|
||||
if (animate) {
|
||||
animateDotScale(targetScale, null /* after */);
|
||||
} else {
|
||||
mDotScale = targetScale;
|
||||
mAnimatingToDotScale = targetScale;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -194,11 +217,11 @@ public class BadgedImageView extends ImageView {
|
||||
}
|
||||
|
||||
/** Sets the position of the 'new' dot, animating it out and back in if requested. */
|
||||
void setDotPosition(boolean onLeft, boolean animate) {
|
||||
if (animate && onLeft != getDotOnLeft() && !isDotHidden()) {
|
||||
animateDot(false /* showDot */, () -> {
|
||||
void setDotPositionOnLeft(boolean onLeft, boolean animate) {
|
||||
if (animate && onLeft != getDotOnLeft() && shouldDrawDot()) {
|
||||
animateDotScale(0f /* showDot */, () -> {
|
||||
setDotOnLeft(onLeft);
|
||||
animateDot(true /* showDot */, null);
|
||||
animateDotScale(1.0f, null /* after */);
|
||||
});
|
||||
} else {
|
||||
setDotOnLeft(onLeft);
|
||||
@@ -209,28 +232,34 @@ public class BadgedImageView extends ImageView {
|
||||
return getDotOnLeft();
|
||||
}
|
||||
|
||||
/** Changes the dot's visibility to match the bubble view's state. */
|
||||
void animateDot() {
|
||||
if (mCurrentDotState == DOT_STATE_DEFAULT) {
|
||||
animateDot(mBubble.showDot(), null);
|
||||
}
|
||||
/** Whether to draw the dot in onDraw(). */
|
||||
private boolean shouldDrawDot() {
|
||||
// Always render the dot if it's animating, since it could be animating out. Otherwise, show
|
||||
// it if the bubble wants to show it, and we aren't suppressing it.
|
||||
return mDotIsAnimating || (mBubble.showDot() && mDotSuppressionFlags.isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the dot to show or hide.
|
||||
* Animates the dot to the given scale, running the optional callback when the animation ends.
|
||||
*/
|
||||
private void animateDot(boolean showDot, Runnable after) {
|
||||
if (mDotDrawn == showDot) {
|
||||
// State is consistent, do nothing.
|
||||
private void animateDotScale(float toScale, @Nullable Runnable after) {
|
||||
mDotIsAnimating = true;
|
||||
|
||||
// Don't restart the animation if we're already animating to the given value.
|
||||
if (mAnimatingToDotScale == toScale || !shouldDrawDot()) {
|
||||
mDotIsAnimating = false;
|
||||
return;
|
||||
}
|
||||
|
||||
setDotState(DOT_STATE_ANIMATING);
|
||||
mAnimatingToDotScale = toScale;
|
||||
|
||||
final boolean showDot = toScale > 0f;
|
||||
|
||||
// Do NOT wait until after animation ends to setShowDot
|
||||
// to avoid overriding more recent showDot states.
|
||||
clearAnimation();
|
||||
animate().setDuration(200)
|
||||
animate()
|
||||
.setDuration(200)
|
||||
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
|
||||
.setUpdateListener((valueAnimator) -> {
|
||||
float fraction = valueAnimator.getAnimatedFraction();
|
||||
@@ -238,7 +267,7 @@ public class BadgedImageView extends ImageView {
|
||||
setDotScale(fraction);
|
||||
}).withEndAction(() -> {
|
||||
setDotScale(showDot ? 1f : 0f);
|
||||
setDotState(DOT_STATE_DEFAULT);
|
||||
mDotIsAnimating = false;
|
||||
if (after != null) {
|
||||
after.run();
|
||||
}
|
||||
|
||||
@@ -247,7 +247,7 @@ class Bubble implements BubbleViewProvider {
|
||||
mExpandedView.update(/* bubble */ this);
|
||||
}
|
||||
if (mIconView != null) {
|
||||
mIconView.update(/* bubble */ this);
|
||||
mIconView.setRenderedBubble(/* bubble */ this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,7 +306,7 @@ class Bubble implements BubbleViewProvider {
|
||||
void markAsAccessedAt(long lastAccessedMillis) {
|
||||
mLastAccessed = lastAccessedMillis;
|
||||
setSuppressNotification(true);
|
||||
setShowDot(false /* show */, true /* animate */);
|
||||
setShowDot(false /* show */);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -346,12 +346,11 @@ class Bubble implements BubbleViewProvider {
|
||||
/**
|
||||
* Sets whether the bubble for this notification should show a dot indicating updated content.
|
||||
*/
|
||||
void setShowDot(boolean showDot, boolean animate) {
|
||||
void setShowDot(boolean showDot) {
|
||||
mShowBubbleUpdateDot = showDot;
|
||||
if (animate && mIconView != null) {
|
||||
mIconView.animateDot();
|
||||
} else if (mIconView != null) {
|
||||
mIconView.invalidate();
|
||||
|
||||
if (mIconView != null) {
|
||||
mIconView.updateDotVisibility(true /* animate */);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -331,14 +331,14 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
||||
@Override
|
||||
public void onZenChanged(int zen) {
|
||||
for (Bubble b : mBubbleData.getBubbles()) {
|
||||
b.setShowDot(b.showInShade(), true /* animate */);
|
||||
b.setShowDot(b.showInShade());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigChanged(ZenModeConfig config) {
|
||||
for (Bubble b : mBubbleData.getBubbles()) {
|
||||
b.setShowDot(b.showInShade(), true /* animate */);
|
||||
b.setShowDot(b.showInShade());
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1101,7 +1101,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
||||
} else if (interceptBubbleDismissal) {
|
||||
Bubble bubble = mBubbleData.getBubbleWithKey(entry.getKey());
|
||||
bubble.setSuppressNotification(true);
|
||||
bubble.setShowDot(false /* show */, true /* animate */);
|
||||
bubble.setShowDot(false /* show */);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@@ -1141,7 +1141,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
||||
Bubble bubbleChild = mBubbleData.getBubbleWithKey(child.getKey());
|
||||
mNotificationGroupManager.onEntryRemoved(bubbleChild.getEntry());
|
||||
bubbleChild.setSuppressNotification(true);
|
||||
bubbleChild.setShowDot(false /* show */, true /* animate */);
|
||||
bubbleChild.setShowDot(false /* show */);
|
||||
} else {
|
||||
// non-bubbled children can be removed
|
||||
for (NotifCallback cb : mCallbacks) {
|
||||
|
||||
@@ -288,7 +288,7 @@ public class BubbleData {
|
||||
boolean isBubbleExpandedAndSelected = mExpanded && mSelectedBubble == bubble;
|
||||
boolean suppress = isBubbleExpandedAndSelected || !showInShade || !bubble.showInShade();
|
||||
bubble.setSuppressNotification(suppress);
|
||||
bubble.setShowDot(!isBubbleExpandedAndSelected /* show */, true /* animate */);
|
||||
bubble.setShowDot(!isBubbleExpandedAndSelected /* show */);
|
||||
|
||||
dispatchPendingChanges();
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ public class BubbleOverflow implements BubbleViewProvider {
|
||||
mPath.transform(matrix);
|
||||
|
||||
mOverflowBtn.setVisibility(GONE);
|
||||
mOverflowBtn.update(this);
|
||||
mOverflowBtn.setRenderedBubble(this);
|
||||
}
|
||||
|
||||
ImageView getBtn() {
|
||||
|
||||
@@ -183,7 +183,7 @@ class BubbleOverflowAdapter extends RecyclerView.Adapter<BubbleOverflowAdapter.V
|
||||
public void onBindViewHolder(ViewHolder vh, int index) {
|
||||
Bubble b = mBubbles.get(index);
|
||||
|
||||
vh.iconView.update(b);
|
||||
vh.iconView.setRenderedBubble(b);
|
||||
vh.iconView.setOnClickListener(view -> {
|
||||
mBubbles.remove(b);
|
||||
notifyDataSetChanged();
|
||||
|
||||
@@ -22,8 +22,6 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN;
|
||||
import static com.android.systemui.Prefs.Key.HAS_SEEN_BUBBLES_EDUCATION;
|
||||
import static com.android.systemui.Prefs.Key.HAS_SEEN_BUBBLES_MANAGE_EDUCATION;
|
||||
import static com.android.systemui.bubbles.BadgedImageView.DOT_STATE_DEFAULT;
|
||||
import static com.android.systemui.bubbles.BadgedImageView.DOT_STATE_SUPPRESSED_FOR_FLYOUT;
|
||||
import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW;
|
||||
import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_USER_EDUCATION;
|
||||
import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
|
||||
@@ -226,7 +224,7 @@ public class BubbleStackView extends FrameLayout {
|
||||
private boolean mIsExpanded;
|
||||
|
||||
/** Whether the stack is currently on the left side of the screen, or animating there. */
|
||||
private boolean mStackOnLeftOrWillBe = false;
|
||||
private boolean mStackOnLeftOrWillBe = true;
|
||||
|
||||
/** Whether a touch gesture, such as a stack/bubble drag or flyout drag, is in progress. */
|
||||
private boolean mIsGestureInProgress = false;
|
||||
@@ -936,9 +934,13 @@ public class BubbleStackView extends FrameLayout {
|
||||
mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
|
||||
}
|
||||
|
||||
if (bubble.getIconView() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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().setDotPosition(
|
||||
bubble.getIconView().setDotPositionOnLeft(
|
||||
!mStackOnLeftOrWillBe /* onLeft */, false /* animate */);
|
||||
|
||||
mBubbleContainer.addView(bubble.getIconView(), 0,
|
||||
@@ -1698,7 +1700,7 @@ public class BubbleStackView extends FrameLayout {
|
||||
|| mBubbleToExpandAfterFlyoutCollapse != null
|
||||
|| bubbleView == null) {
|
||||
if (bubbleView != null) {
|
||||
bubbleView.setDotState(DOT_STATE_DEFAULT);
|
||||
bubbleView.removeDotSuppressionFlag(BadgedImageView.SuppressionFlag.FLYOUT_VISIBLE);
|
||||
}
|
||||
// Skip the message if none exists, we're expanded or animating expansion, or we're
|
||||
// about to expand a bubble from the previous tapped flyout, or if bubble view is null.
|
||||
@@ -1717,12 +1719,16 @@ public class BubbleStackView extends FrameLayout {
|
||||
mBubbleData.setExpanded(true);
|
||||
mBubbleToExpandAfterFlyoutCollapse = null;
|
||||
}
|
||||
bubbleView.setDotState(DOT_STATE_DEFAULT);
|
||||
|
||||
// Stop suppressing the dot now that the flyout has morphed into the dot.
|
||||
bubbleView.removeDotSuppressionFlag(
|
||||
BadgedImageView.SuppressionFlag.FLYOUT_VISIBLE);
|
||||
};
|
||||
mFlyout.setVisibility(INVISIBLE);
|
||||
|
||||
// Don't show the dot when we're animating the flyout
|
||||
bubbleView.setDotState(DOT_STATE_SUPPRESSED_FOR_FLYOUT);
|
||||
// Suppress the dot when we are animating the flyout.
|
||||
bubbleView.addDotSuppressionFlag(
|
||||
BadgedImageView.SuppressionFlag.FLYOUT_VISIBLE);
|
||||
|
||||
// Start flyout expansion. Post in case layout isn't complete and getWidth returns 0.
|
||||
post(() -> {
|
||||
@@ -1743,6 +1749,11 @@ public class BubbleStackView extends FrameLayout {
|
||||
};
|
||||
mFlyout.postDelayed(mAnimateInFlyout, 200);
|
||||
};
|
||||
|
||||
if (bubble.getIconView() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mFlyout.setupFlyoutStartingAsDot(flyoutMessage,
|
||||
mStackAnimationController.getStackPosition(), getWidth(),
|
||||
mStackAnimationController.isStackOnLeftSide(),
|
||||
@@ -1877,9 +1888,19 @@ public class BubbleStackView extends FrameLayout {
|
||||
for (int i = 0; i < bubbleCount; i++) {
|
||||
BadgedImageView bv = (BadgedImageView) mBubbleContainer.getChildAt(i);
|
||||
bv.setZ((mMaxBubbles * mBubbleElevation) - i);
|
||||
|
||||
// If the dot is on the left, and so is the stack, we need to change the dot position.
|
||||
if (bv.getDotPositionOnLeft() == mStackOnLeftOrWillBe) {
|
||||
bv.setDotPosition(!mStackOnLeftOrWillBe, animate);
|
||||
bv.setDotPositionOnLeft(!mStackOnLeftOrWillBe, animate);
|
||||
}
|
||||
|
||||
if (!mIsExpanded && i > 0) {
|
||||
// If we're collapsed and this bubble is behind other bubbles, suppress its dot.
|
||||
bv.addDotSuppressionFlag(
|
||||
BadgedImageView.SuppressionFlag.BEHIND_STACK);
|
||||
} else {
|
||||
bv.removeDotSuppressionFlag(
|
||||
BadgedImageView.SuppressionFlag.BEHIND_STACK);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,7 +287,7 @@ public class StackAnimationController extends
|
||||
/** Whether the stack is on the left side of the screen. */
|
||||
public boolean isStackOnLeftSide() {
|
||||
if (mLayout == null || !isStackPositionSet()) {
|
||||
return false;
|
||||
return true; // Default to left, which is where it starts by default.
|
||||
}
|
||||
|
||||
float stackCenter = mStackPosition.x + mBubbleBitmapSize / 2;
|
||||
|
||||
Reference in New Issue
Block a user