Merge changes from topic "mm_bubbleInfoFlags" into udc-qpr-dev
* changes: Show / hide the "update" dot on bubbles in bubble bar Handle any image / label changes for bubble updates in bubble bar
This commit is contained in:
@@ -33,6 +33,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_S
|
|||||||
|
|
||||||
import android.annotation.BinderThread;
|
import android.annotation.BinderThread;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
|
import android.app.Notification;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.content.pm.LauncherApps;
|
import android.content.pm.LauncherApps;
|
||||||
@@ -243,17 +244,21 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
|||||||
BUBBLE_STATE_EXECUTOR.execute(() -> {
|
BUBBLE_STATE_EXECUTOR.execute(() -> {
|
||||||
createAndAddOverflowIfNeeded();
|
createAndAddOverflowIfNeeded();
|
||||||
if (update.addedBubble != null) {
|
if (update.addedBubble != null) {
|
||||||
viewUpdate.addedBubble = populateBubble(update.addedBubble, mContext, mBarView);
|
viewUpdate.addedBubble = populateBubble(mContext, update.addedBubble, mBarView,
|
||||||
|
null /* existingBubble */);
|
||||||
}
|
}
|
||||||
if (update.updatedBubble != null) {
|
if (update.updatedBubble != null) {
|
||||||
|
BubbleBarBubble existingBubble = mBubbles.get(update.updatedBubble.getKey());
|
||||||
viewUpdate.updatedBubble =
|
viewUpdate.updatedBubble =
|
||||||
populateBubble(update.updatedBubble, mContext, mBarView);
|
populateBubble(mContext, update.updatedBubble, mBarView,
|
||||||
|
existingBubble);
|
||||||
}
|
}
|
||||||
if (update.currentBubbleList != null && !update.currentBubbleList.isEmpty()) {
|
if (update.currentBubbleList != null && !update.currentBubbleList.isEmpty()) {
|
||||||
List<BubbleBarBubble> currentBubbles = new ArrayList<>();
|
List<BubbleBarBubble> currentBubbles = new ArrayList<>();
|
||||||
for (int i = 0; i < update.currentBubbleList.size(); i++) {
|
for (int i = 0; i < update.currentBubbleList.size(); i++) {
|
||||||
BubbleBarBubble b =
|
BubbleBarBubble b =
|
||||||
populateBubble(update.currentBubbleList.get(i), mContext, mBarView);
|
populateBubble(mContext, update.currentBubbleList.get(i), mBarView,
|
||||||
|
null /* existingBubble */);
|
||||||
currentBubbles.add(b);
|
currentBubbles.add(b);
|
||||||
}
|
}
|
||||||
viewUpdate.currentBubbles = currentBubbles;
|
viewUpdate.currentBubbles = currentBubbles;
|
||||||
@@ -315,9 +320,11 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
|||||||
mBubbleStashedHandleViewController.setHiddenForBubbles(mBubbles.isEmpty());
|
mBubbleStashedHandleViewController.setHiddenForBubbles(mBubbles.isEmpty());
|
||||||
|
|
||||||
if (update.updatedBubble != null) {
|
if (update.updatedBubble != null) {
|
||||||
// TODO: (b/269670235) handle updates:
|
// Updates mean the dot state may have changed; any other changes were updated in
|
||||||
// (1) if content / icons change -- requires reload & add back in place
|
// the populateBubble step.
|
||||||
// (2) if showing update dot changes -- tell the view to hide / show the dot
|
BubbleBarBubble bb = mBubbles.get(update.updatedBubble.getKey());
|
||||||
|
// If we're not stashed, we're visible so animate
|
||||||
|
bb.getView().updateDotVisibility(!mBubbleStashController.isStashed() /* animate */);
|
||||||
}
|
}
|
||||||
if (update.bubbleKeysInOrder != null && !update.bubbleKeysInOrder.isEmpty()) {
|
if (update.bubbleKeysInOrder != null && !update.bubbleKeysInOrder.isEmpty()) {
|
||||||
// Create the new list
|
// Create the new list
|
||||||
@@ -362,7 +369,13 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
|||||||
if (getSelectedBubbleKey() != null) {
|
if (getSelectedBubbleKey() != null) {
|
||||||
int[] bubbleBarCoords = mBarView.getLocationOnScreen();
|
int[] bubbleBarCoords = mBarView.getLocationOnScreen();
|
||||||
if (mSelectedBubble instanceof BubbleBarBubble) {
|
if (mSelectedBubble instanceof BubbleBarBubble) {
|
||||||
// TODO (b/269670235): hide the update dot on the view if needed.
|
// Because we've visited this bubble, we should suppress the notification.
|
||||||
|
// This is updated on WMShell side when we show the bubble, but that update isn't
|
||||||
|
// passed to launcher, instead we apply it directly here.
|
||||||
|
BubbleInfo info = ((BubbleBarBubble) mSelectedBubble).getInfo();
|
||||||
|
info.setFlags(
|
||||||
|
info.getFlags() | Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
|
||||||
|
mSelectedBubble.getView().updateDotVisibility(true /* animate */);
|
||||||
}
|
}
|
||||||
mSystemUiProxy.showBubble(getSelectedBubbleKey(),
|
mSystemUiProxy.showBubble(getSelectedBubbleKey(),
|
||||||
bubbleBarCoords[0], bubbleBarCoords[1]);
|
bubbleBarCoords[0], bubbleBarCoords[1]);
|
||||||
@@ -407,7 +420,8 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
|||||||
//
|
//
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private BubbleBarBubble populateBubble(BubbleInfo b, Context context, BubbleBarView bbv) {
|
private BubbleBarBubble populateBubble(Context context, BubbleInfo b, BubbleBarView bbv,
|
||||||
|
@Nullable BubbleBarBubble existingBubble) {
|
||||||
String appName;
|
String appName;
|
||||||
Bitmap badgeBitmap;
|
Bitmap badgeBitmap;
|
||||||
Bitmap bubbleBitmap;
|
Bitmap bubbleBitmap;
|
||||||
@@ -476,16 +490,27 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
|||||||
iconPath.transform(matrix);
|
iconPath.transform(matrix);
|
||||||
dotPath = iconPath;
|
dotPath = iconPath;
|
||||||
dotColor = ColorUtils.blendARGB(badgeBitmapInfo.color,
|
dotColor = ColorUtils.blendARGB(badgeBitmapInfo.color,
|
||||||
Color.WHITE, WHITE_SCRIM_ALPHA);
|
Color.WHITE, WHITE_SCRIM_ALPHA / 255f);
|
||||||
|
|
||||||
LayoutInflater inflater = LayoutInflater.from(context);
|
if (existingBubble == null) {
|
||||||
BubbleView bubbleView = (BubbleView) inflater.inflate(
|
LayoutInflater inflater = LayoutInflater.from(context);
|
||||||
R.layout.bubblebar_item_view, bbv, false /* attachToRoot */);
|
BubbleView bubbleView = (BubbleView) inflater.inflate(
|
||||||
|
R.layout.bubblebar_item_view, bbv, false /* attachToRoot */);
|
||||||
|
|
||||||
BubbleBarBubble bubble = new BubbleBarBubble(b, bubbleView,
|
BubbleBarBubble bubble = new BubbleBarBubble(b, bubbleView,
|
||||||
badgeBitmap, bubbleBitmap, dotColor, dotPath, appName);
|
badgeBitmap, bubbleBitmap, dotColor, dotPath, appName);
|
||||||
bubbleView.setBubble(bubble);
|
bubbleView.setBubble(bubble);
|
||||||
return bubble;
|
return bubble;
|
||||||
|
} else {
|
||||||
|
// If we already have a bubble (so it already has an inflated view), update it.
|
||||||
|
existingBubble.setInfo(b);
|
||||||
|
existingBubble.setBadge(badgeBitmap);
|
||||||
|
existingBubble.setIcon(bubbleBitmap);
|
||||||
|
existingBubble.setDotColor(dotColor);
|
||||||
|
existingBubble.setDotPath(dotPath);
|
||||||
|
existingBubble.setAppName(appName);
|
||||||
|
return existingBubble;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private BubbleBarOverflow createOverflow(Context context) {
|
private BubbleBarOverflow createOverflow(Context context) {
|
||||||
|
|||||||
@@ -20,18 +20,18 @@ import android.graphics.Path
|
|||||||
import com.android.wm.shell.common.bubbles.BubbleInfo
|
import com.android.wm.shell.common.bubbles.BubbleInfo
|
||||||
|
|
||||||
/** An entity in the bubble bar. */
|
/** An entity in the bubble bar. */
|
||||||
sealed class BubbleBarItem(open val key: String, open val view: BubbleView)
|
sealed class BubbleBarItem(open var key: String, open var view: BubbleView)
|
||||||
|
|
||||||
/** Contains state info about a bubble in the bubble bar as well as presentation information. */
|
/** Contains state info about a bubble in the bubble bar as well as presentation information. */
|
||||||
data class BubbleBarBubble(
|
data class BubbleBarBubble(
|
||||||
val info: BubbleInfo,
|
var info: BubbleInfo,
|
||||||
override val view: BubbleView,
|
override var view: BubbleView,
|
||||||
val badge: Bitmap,
|
var badge: Bitmap,
|
||||||
val icon: Bitmap,
|
var icon: Bitmap,
|
||||||
val dotColor: Int,
|
var dotColor: Int,
|
||||||
val dotPath: Path,
|
var dotPath: Path,
|
||||||
val appName: String
|
var appName: String
|
||||||
) : BubbleBarItem(info.key, view)
|
) : BubbleBarItem(info.key, view)
|
||||||
|
|
||||||
/** Represents the overflow bubble in the bubble bar. */
|
/** Represents the overflow bubble in the bubble bar. */
|
||||||
data class BubbleBarOverflow(override val view: BubbleView) : BubbleBarItem("Overflow", view)
|
data class BubbleBarOverflow(override var view: BubbleView) : BubbleBarItem("Overflow", view)
|
||||||
|
|||||||
@@ -234,6 +234,7 @@ public class BubbleBarView extends FrameLayout {
|
|||||||
final float collapsedWidth = collapsedWidth();
|
final float collapsedWidth = collapsedWidth();
|
||||||
int bubbleCount = getChildCount();
|
int bubbleCount = getChildCount();
|
||||||
final float ty = (mBubbleBarBounds.height() - mIconSize) / 2f;
|
final float ty = (mBubbleBarBounds.height() - mIconSize) / 2f;
|
||||||
|
final boolean animate = getVisibility() == VISIBLE;
|
||||||
for (int i = 0; i < bubbleCount; i++) {
|
for (int i = 0; i < bubbleCount; i++) {
|
||||||
BubbleView bv = (BubbleView) getChildAt(i);
|
BubbleView bv = (BubbleView) getChildAt(i);
|
||||||
bv.setTranslationY(ty);
|
bv.setTranslationY(ty);
|
||||||
@@ -251,16 +252,14 @@ public class BubbleBarView extends FrameLayout {
|
|||||||
if (widthState == 1f) {
|
if (widthState == 1f) {
|
||||||
bv.setZ(0);
|
bv.setZ(0);
|
||||||
}
|
}
|
||||||
bv.showBadge();
|
// When we're expanded, we're not stacked so we're not behind the stack
|
||||||
|
bv.setBehindStack(false, animate);
|
||||||
} else {
|
} else {
|
||||||
final float targetX = currentWidth - collapsedWidth + collapsedX;
|
final float targetX = currentWidth - collapsedWidth + collapsedX;
|
||||||
bv.setTranslationX(widthState * (expandedX - targetX) + targetX);
|
bv.setTranslationX(widthState * (expandedX - targetX) + targetX);
|
||||||
bv.setZ((MAX_BUBBLES * mBubbleElevation) - i);
|
bv.setZ((MAX_BUBBLES * mBubbleElevation) - i);
|
||||||
if (i > 0) {
|
// If we're not the first bubble we're behind the stack
|
||||||
bv.hideBadge();
|
bv.setBehindStack(i > 0, animate);
|
||||||
} else {
|
|
||||||
bv.showBadge();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ package com.android.launcher3.taskbar.bubbles;
|
|||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Outline;
|
import android.graphics.Outline;
|
||||||
|
import android.graphics.Rect;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -28,10 +30,13 @@ import android.widget.ImageView;
|
|||||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||||
|
|
||||||
import com.android.launcher3.R;
|
import com.android.launcher3.R;
|
||||||
|
import com.android.launcher3.icons.DotRenderer;
|
||||||
import com.android.launcher3.icons.IconNormalizer;
|
import com.android.launcher3.icons.IconNormalizer;
|
||||||
|
import com.android.wm.shell.animation.Interpolators;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
|
||||||
// TODO: (b/276978250) This is will be similar to WMShell's BadgedImageView, it'd be nice to share.
|
// TODO: (b/276978250) This is will be similar to WMShell's BadgedImageView, it'd be nice to share.
|
||||||
// TODO: (b/269670235) currently this doesn't show the 'update dot'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* View that displays a bubble icon, along with an app badge on either the left or
|
* View that displays a bubble icon, along with an app badge on either the left or
|
||||||
@@ -39,14 +44,42 @@ import com.android.launcher3.icons.IconNormalizer;
|
|||||||
*/
|
*/
|
||||||
public class BubbleView extends ConstraintLayout {
|
public class BubbleView extends ConstraintLayout {
|
||||||
|
|
||||||
// TODO: (b/269670235) currently we don't render the 'update dot', this will be used for that.
|
|
||||||
public static final int DEFAULT_PATH_SIZE = 100;
|
public static final int DEFAULT_PATH_SIZE = 100;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flags that suppress the visibility of the 'new' dot or the app badge, for one reason or
|
||||||
|
* another. If any of these flags are set, the dot will not be shown.
|
||||||
|
* If {@link SuppressionFlag#BEHIND_STACK} then the app badge will not be shown.
|
||||||
|
*/
|
||||||
|
enum SuppressionFlag {
|
||||||
|
// TODO: (b/277815200) implement flyout
|
||||||
|
// 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,
|
||||||
|
}
|
||||||
|
|
||||||
|
private final EnumSet<SuppressionFlag> mSuppressionFlags =
|
||||||
|
EnumSet.noneOf(SuppressionFlag.class);
|
||||||
|
|
||||||
private final ImageView mBubbleIcon;
|
private final ImageView mBubbleIcon;
|
||||||
private final ImageView mAppIcon;
|
private final ImageView mAppIcon;
|
||||||
private final int mBubbleSize;
|
private final int mBubbleSize;
|
||||||
|
|
||||||
|
private DotRenderer mDotRenderer;
|
||||||
|
private DotRenderer.DrawParams mDrawParams;
|
||||||
|
private int mDotColor;
|
||||||
|
private Rect mTempBounds = new Rect();
|
||||||
|
|
||||||
|
// Whether the dot is animating
|
||||||
|
private boolean mDotIsAnimating;
|
||||||
|
// What scale value the dot is animating to
|
||||||
|
private float mAnimatingToDotScale;
|
||||||
|
// The current scale value of the dot
|
||||||
|
private float mDotScale;
|
||||||
|
|
||||||
// TODO: (b/273310265) handle RTL
|
// TODO: (b/273310265) handle RTL
|
||||||
|
// Whether the bubbles are positioned on the left or right side of the screen
|
||||||
private boolean mOnLeft = false;
|
private boolean mOnLeft = false;
|
||||||
|
|
||||||
private BubbleBarItem mBubble;
|
private BubbleBarItem mBubble;
|
||||||
@@ -75,6 +108,8 @@ public class BubbleView extends ConstraintLayout {
|
|||||||
mBubbleIcon = findViewById(R.id.icon_view);
|
mBubbleIcon = findViewById(R.id.icon_view);
|
||||||
mAppIcon = findViewById(R.id.app_icon_view);
|
mAppIcon = findViewById(R.id.app_icon_view);
|
||||||
|
|
||||||
|
mDrawParams = new DotRenderer.DrawParams();
|
||||||
|
|
||||||
setFocusable(true);
|
setFocusable(true);
|
||||||
setClickable(true);
|
setClickable(true);
|
||||||
setOutlineProvider(new ViewOutlineProvider() {
|
setOutlineProvider(new ViewOutlineProvider() {
|
||||||
@@ -91,17 +126,43 @@ public class BubbleView extends ConstraintLayout {
|
|||||||
outline.setOval(inset, inset, inset + normalizedSize, inset + normalizedSize);
|
outline.setOval(inset, inset, inset + normalizedSize, inset + normalizedSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispatchDraw(Canvas canvas) {
|
||||||
|
super.dispatchDraw(canvas);
|
||||||
|
|
||||||
|
if (!shouldDrawDot()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDrawingRect(mTempBounds);
|
||||||
|
|
||||||
|
mDrawParams.dotColor = mDotColor;
|
||||||
|
mDrawParams.iconBounds = mTempBounds;
|
||||||
|
mDrawParams.leftAlign = mOnLeft;
|
||||||
|
mDrawParams.scale = mDotScale;
|
||||||
|
|
||||||
|
mDotRenderer.draw(canvas, mDrawParams);
|
||||||
|
}
|
||||||
|
|
||||||
/** Sets the bubble being rendered in this view. */
|
/** Sets the bubble being rendered in this view. */
|
||||||
void setBubble(BubbleBarBubble bubble) {
|
void setBubble(BubbleBarBubble bubble) {
|
||||||
mBubble = bubble;
|
mBubble = bubble;
|
||||||
mBubbleIcon.setImageBitmap(bubble.getIcon());
|
mBubbleIcon.setImageBitmap(bubble.getIcon());
|
||||||
mAppIcon.setImageBitmap(bubble.getBadge());
|
mAppIcon.setImageBitmap(bubble.getBadge());
|
||||||
|
mDotColor = bubble.getDotColor();
|
||||||
|
mDotRenderer = new DotRenderer(mBubbleSize, bubble.getDotPath(), DEFAULT_PATH_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets that this bubble represents the overflow. The overflow appears in the list of bubbles
|
||||||
|
* but does not represent app content, instead it shows recent bubbles that couldn't fit into
|
||||||
|
* the list of bubbles. It doesn't show an app icon because it is part of system UI / doesn't
|
||||||
|
* come from an app.
|
||||||
|
*/
|
||||||
void setOverflow(BubbleBarOverflow overflow, Bitmap bitmap) {
|
void setOverflow(BubbleBarOverflow overflow, Bitmap bitmap) {
|
||||||
mBubble = overflow;
|
mBubble = overflow;
|
||||||
mBubbleIcon.setImageBitmap(bitmap);
|
mBubbleIcon.setImageBitmap(bitmap);
|
||||||
hideBadge();
|
mAppIcon.setVisibility(GONE); // Overflow doesn't show the app badge
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the bubble being rendered in this view. */
|
/** Returns the bubble being rendered in this view. */
|
||||||
@@ -110,38 +171,102 @@ public class BubbleView extends ConstraintLayout {
|
|||||||
return mBubble;
|
return mBubble;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Shows the app badge on this bubble. */
|
void updateDotVisibility(boolean animate) {
|
||||||
void showBadge() {
|
final float targetScale = shouldDrawDot() ? 1f : 0f;
|
||||||
|
if (animate) {
|
||||||
|
animateDotScale();
|
||||||
|
} else {
|
||||||
|
mDotScale = targetScale;
|
||||||
|
mAnimatingToDotScale = targetScale;
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateBadgeVisibility() {
|
||||||
if (mBubble instanceof BubbleBarOverflow) {
|
if (mBubble instanceof BubbleBarOverflow) {
|
||||||
// The overflow bubble does not have a badge, so just bail.
|
// The overflow bubble does not have a badge, so just bail.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
BubbleBarBubble bubble = (BubbleBarBubble) mBubble;
|
BubbleBarBubble bubble = (BubbleBarBubble) mBubble;
|
||||||
|
|
||||||
Bitmap appBadgeBitmap = bubble.getBadge();
|
Bitmap appBadgeBitmap = bubble.getBadge();
|
||||||
if (appBadgeBitmap == null) {
|
int translationX = mOnLeft
|
||||||
mAppIcon.setVisibility(GONE);
|
? -(bubble.getIcon().getWidth() - appBadgeBitmap.getWidth())
|
||||||
|
: 0;
|
||||||
|
mAppIcon.setTranslationX(translationX);
|
||||||
|
mAppIcon.setVisibility(isBehindStack() ? GONE : VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets whether this bubble is in the stack & not the first bubble. **/
|
||||||
|
void setBehindStack(boolean behindStack, boolean animate) {
|
||||||
|
if (behindStack) {
|
||||||
|
mSuppressionFlags.add(SuppressionFlag.BEHIND_STACK);
|
||||||
|
} else {
|
||||||
|
mSuppressionFlags.remove(SuppressionFlag.BEHIND_STACK);
|
||||||
|
}
|
||||||
|
updateDotVisibility(animate);
|
||||||
|
updateBadgeVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Whether this bubble is in the stack & not the first bubble. **/
|
||||||
|
boolean isBehindStack() {
|
||||||
|
return mSuppressionFlags.contains(SuppressionFlag.BEHIND_STACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Whether the dot indicating unseen content in a bubble should be shown. */
|
||||||
|
private boolean shouldDrawDot() {
|
||||||
|
boolean bubbleHasUnseenContent = mBubble != null
|
||||||
|
&& mBubble instanceof BubbleBarBubble
|
||||||
|
&& mSuppressionFlags.isEmpty()
|
||||||
|
&& !((BubbleBarBubble) mBubble).getInfo().isNotificationSuppressed();
|
||||||
|
|
||||||
|
// 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 bubbleHasUnseenContent || mDotIsAnimating;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** How big the dot should be, fraction from 0 to 1. */
|
||||||
|
private void setDotScale(float fraction) {
|
||||||
|
mDotScale = fraction;
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Animates the dot to the given scale.
|
||||||
|
*/
|
||||||
|
private void animateDotScale() {
|
||||||
|
float toScale = shouldDrawDot() ? 1f : 0f;
|
||||||
|
mDotIsAnimating = true;
|
||||||
|
|
||||||
|
// Don't restart the animation if we're already animating to the given value.
|
||||||
|
if (mAnimatingToDotScale == toScale || !shouldDrawDot()) {
|
||||||
|
mDotIsAnimating = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int translationX;
|
mAnimatingToDotScale = toScale;
|
||||||
if (mOnLeft) {
|
|
||||||
translationX = -(bubble.getIcon().getWidth() - appBadgeBitmap.getWidth());
|
|
||||||
} else {
|
|
||||||
translationX = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
mAppIcon.setTranslationX(translationX);
|
final boolean showDot = toScale > 0f;
|
||||||
mAppIcon.setVisibility(VISIBLE);
|
|
||||||
|
// Do NOT wait until after animation ends to setShowDot
|
||||||
|
// to avoid overriding more recent showDot states.
|
||||||
|
clearAnimation();
|
||||||
|
animate()
|
||||||
|
.setDuration(200)
|
||||||
|
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
|
||||||
|
.setUpdateListener((valueAnimator) -> {
|
||||||
|
float fraction = valueAnimator.getAnimatedFraction();
|
||||||
|
fraction = showDot ? fraction : 1f - fraction;
|
||||||
|
setDotScale(fraction);
|
||||||
|
}).withEndAction(() -> {
|
||||||
|
setDotScale(showDot ? 1f : 0f);
|
||||||
|
mDotIsAnimating = false;
|
||||||
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Hides the app badge on this bubble. */
|
|
||||||
void hideBadge() {
|
|
||||||
mAppIcon.setVisibility(GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "BubbleView{" + mBubble + "}";
|
String toString = mBubble != null ? mBubble.getKey() : "null";
|
||||||
|
return "BubbleView{" + toString + "}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user