Merge "Assorted dot-wrangling." into rvc-dev am: eb7da42d27 am: 3c39fd60a0

Change-Id: Ieb82b88faf8ad2de6af97c38640ee95b4be12281
This commit is contained in:
Josh Tsuji
2020-03-27 15:04:35 +00:00
committed by Automerger Merge Worker
8 changed files with 116 additions and 67 deletions

View File

@@ -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();
}

View File

@@ -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 */);
}
}

View File

@@ -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) {

View File

@@ -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();
}

View File

@@ -112,7 +112,7 @@ public class BubbleOverflow implements BubbleViewProvider {
mPath.transform(matrix);
mOverflowBtn.setVisibility(GONE);
mOverflowBtn.update(this);
mOverflowBtn.setRenderedBubble(this);
}
ImageView getBtn() {

View File

@@ -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();

View File

@@ -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);
}
}
}

View File

@@ -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;