Add support for color extracted notification dots

Changing the badge_color in colors.xml to transparent
will cause them to be color extracted.

When an extracted color is used in the IconPalette, we
desaturate the background. Otherwise we respect the
exact color specified in colors.xml.

Change-Id: Ie82d0c5335fa5f24d4cc47766e4c1719c4916f8b
This commit is contained in:
Tony Wickham
2017-06-07 14:32:23 -07:00
parent 85b64c7da0
commit 7092db0241
7 changed files with 66 additions and 26 deletions
+1
View File
@@ -42,6 +42,7 @@
<color name="notification_color_beneath">#E0E0E0</color> <!-- Gray 300 --> <color name="notification_color_beneath">#E0E0E0</color> <!-- Gray 300 -->
<color name="badge_color">#1DE9B6</color> <!-- Teal A400 --> <color name="badge_color">#1DE9B6</color> <!-- Teal A400 -->
<color name="folder_badge_color">#1DE9B6</color> <!-- Teal A400 -->
<!-- System shortcuts --> <!-- System shortcuts -->
<color name="system_shortcuts_icon_color">@android:color/tertiary_text_light</color> <color name="system_shortcuts_icon_color">@android:color/tertiary_text_light</color>
+10 -3
View File
@@ -96,7 +96,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver {
private BadgeInfo mBadgeInfo; private BadgeInfo mBadgeInfo;
private BadgeRenderer mBadgeRenderer; private BadgeRenderer mBadgeRenderer;
private IconPalette mIconPalette; private IconPalette mBadgePalette;
private float mBadgeScale; private float mBadgeScale;
private boolean mForceHideBadge; private boolean mForceHideBadge;
private Point mTempSpaceForBadgeOffset = new Point(); private Point mTempSpaceForBadgeOffset = new Point();
@@ -453,7 +453,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver {
final int scrollX = getScrollX(); final int scrollX = getScrollX();
final int scrollY = getScrollY(); final int scrollY = getScrollY();
canvas.translate(scrollX, scrollY); canvas.translate(scrollX, scrollY);
mBadgeRenderer.draw(canvas, mBadgeInfo, mTempIconBounds, mBadgeScale, mBadgeRenderer.draw(canvas, mBadgePalette, mBadgeInfo, mTempIconBounds, mBadgeScale,
mTempSpaceForBadgeOffset); mTempSpaceForBadgeOffset);
canvas.translate(-scrollX, -scrollY); canvas.translate(-scrollX, -scrollY);
} }
@@ -578,7 +578,10 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver {
float newBadgeScale = isBadged ? 1f : 0; float newBadgeScale = isBadged ? 1f : 0;
mBadgeRenderer = mLauncher.getDeviceProfile().mBadgeRenderer; mBadgeRenderer = mLauncher.getDeviceProfile().mBadgeRenderer;
if (wasBadged || isBadged) { if (wasBadged || isBadged) {
mIconPalette = ((FastBitmapDrawable) mIcon).getIconPalette(); mBadgePalette = IconPalette.getBadgePalette(getResources());
if (mBadgePalette == null) {
mBadgePalette = ((FastBitmapDrawable) mIcon).getIconPalette();
}
// Animate when a badge is first added or when it is removed. // Animate when a badge is first added or when it is removed.
if (animate && (wasBadged ^ isBadged) && isShown()) { if (animate && (wasBadged ^ isBadged) && isShown()) {
ObjectAnimator.ofFloat(this, BADGE_SCALE_PROPERTY, newBadgeScale).start(); ObjectAnimator.ofFloat(this, BADGE_SCALE_PROPERTY, newBadgeScale).start();
@@ -590,6 +593,10 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver {
} }
} }
public IconPalette getBadgePalette() {
return mBadgePalette;
}
/** /**
* Sets the icon for this view based on the layout direction. * Sets the icon for this view based on the layout direction.
*/ */
@@ -124,7 +124,7 @@ public class FastBitmapDrawable extends Drawable {
public IconPalette getIconPalette() { public IconPalette getIconPalette() {
if (mIconPalette == null) { if (mIconPalette == null) {
mIconPalette = IconPalette.fromDominantColor(Utilities mIconPalette = IconPalette.fromDominantColor(Utilities
.findDominantColorByHue(mBitmap, 20)); .findDominantColorByHue(mBitmap, 20), true /* desaturateBackground */);
} }
return mIconPalette; return mIconPalette;
} }
@@ -63,7 +63,6 @@ public class BadgeRenderer {
private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG
| Paint.FILTER_BITMAP_FLAG); | Paint.FILTER_BITMAP_FLAG);
private final SparseArray<Bitmap> mBackgroundsWithShadow; private final SparseArray<Bitmap> mBackgroundsWithShadow;
private final IconPalette mIconPalette;
public BadgeRenderer(Context context, int iconSizePx) { public BadgeRenderer(Context context, int iconSizePx) {
mContext = context; mContext = context;
@@ -83,25 +82,24 @@ public class BadgeRenderer {
mTextHeight = tempTextHeight.height(); mTextHeight = tempTextHeight.height();
mBackgroundsWithShadow = new SparseArray<>(3); mBackgroundsWithShadow = new SparseArray<>(3);
mIconPalette = IconPalette.fromDominantColor(context.getColor(R.color.badge_color));
} }
/** /**
* Draw a circle in the top right corner of the given bounds, and draw * Draw a circle in the top right corner of the given bounds, and draw
* {@link BadgeInfo#getNotificationCount()} on top of the circle. * {@link BadgeInfo#getNotificationCount()} on top of the circle.
* @param palette The colors (based on the icon) to use for the badge.
* @param badgeInfo Contains data to draw on the badge. Could be null if we are animating out. * @param badgeInfo Contains data to draw on the badge. Could be null if we are animating out.
* @param iconBounds The bounds of the icon being badged. * @param iconBounds The bounds of the icon being badged.
* @param badgeScale The progress of the animation, from 0 to 1. * @param badgeScale The progress of the animation, from 0 to 1.
* @param spaceForOffset How much space is available to offset the badge up and to the right. * @param spaceForOffset How much space is available to offset the badge up and to the right.
*/ */
public void draw(Canvas canvas, @Nullable BadgeInfo badgeInfo, public void draw(Canvas canvas, IconPalette palette, @Nullable BadgeInfo badgeInfo,
Rect iconBounds, float badgeScale, Point spaceForOffset) { Rect iconBounds, float badgeScale, Point spaceForOffset) {
mTextPaint.setColor(mIconPalette.textColor); mTextPaint.setColor(palette.textColor);
IconDrawer iconDrawer = badgeInfo != null && badgeInfo.isIconLarge() IconDrawer iconDrawer = badgeInfo != null && badgeInfo.isIconLarge()
? mLargeIconDrawer : mSmallIconDrawer; ? mLargeIconDrawer : mSmallIconDrawer;
Shader icon = badgeInfo == null ? null : badgeInfo.getNotificationIconForBadge( Shader icon = badgeInfo == null ? null : badgeInfo.getNotificationIconForBadge(
mContext, mIconPalette.backgroundColor, mSize, iconDrawer.mPadding); mContext, palette.backgroundColor, mSize, iconDrawer.mPadding);
String notificationCount = badgeInfo == null ? "0" String notificationCount = badgeInfo == null ? "0"
: String.valueOf(badgeInfo.getNotificationCount()); : String.valueOf(badgeInfo.getNotificationCount());
int numChars = notificationCount.length(); int numChars = notificationCount.length();
@@ -127,7 +125,7 @@ public class BadgeRenderer {
canvas.translate(badgeCenterX + offsetX, badgeCenterY - offsetY); canvas.translate(badgeCenterX + offsetX, badgeCenterY - offsetY);
canvas.scale(badgeScale, badgeScale); canvas.scale(badgeScale, badgeScale);
// Prepare the background and shadow and possible stacking effect. // Prepare the background and shadow and possible stacking effect.
mBackgroundPaint.setColorFilter(mIconPalette.backgroundColorMatrixFilter); mBackgroundPaint.setColorFilter(palette.backgroundColorMatrixFilter);
int backgroundWithShadowSize = backgroundWithShadow.getHeight(); // Same as width. int backgroundWithShadowSize = backgroundWithShadow.getHeight(); // Same as width.
boolean shouldStack = !isDot && badgeInfo != null boolean shouldStack = !isDot && badgeInfo != null
&& badgeInfo.getNotificationKeys().size() > 1; && badgeInfo.getNotificationKeys().size() > 1;
@@ -149,7 +147,7 @@ public class BadgeRenderer {
-backgroundWithShadowSize / 2, mBackgroundPaint); -backgroundWithShadowSize / 2, mBackgroundPaint);
iconDrawer.drawIcon(icon, canvas); iconDrawer.drawIcon(icon, canvas);
} else if (isDot) { } else if (isDot) {
mBackgroundPaint.setColorFilter(mIconPalette.saturatedBackgroundColorMatrixFilter); mBackgroundPaint.setColorFilter(palette.saturatedBackgroundColorMatrixFilter);
canvas.drawBitmap(backgroundWithShadow, -backgroundWithShadowSize / 2, canvas.drawBitmap(backgroundWithShadow, -backgroundWithShadowSize / 2,
-backgroundWithShadowSize / 2, mBackgroundPaint); -backgroundWithShadowSize / 2, mBackgroundPaint);
} }
@@ -75,6 +75,7 @@ import com.android.launcher3.badge.FolderBadgeInfo;
import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragView; import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.graphics.IconPalette;
import com.android.launcher3.util.Thunk; import com.android.launcher3.util.Thunk;
import java.util.ArrayList; import java.util.ArrayList;
@@ -885,7 +886,8 @@ public class FolderIcon extends FrameLayout implements FolderListener {
// If we are animating to the accepting state, animate the badge out. // If we are animating to the accepting state, animate the badge out.
float badgeScale = Math.max(0, mBadgeScale - mBackground.getScaleProgress()); float badgeScale = Math.max(0, mBadgeScale - mBackground.getScaleProgress());
mTempSpaceForBadgeOffset.set(getWidth() - mTempBounds.right, mTempBounds.top); mTempSpaceForBadgeOffset.set(getWidth() - mTempBounds.right, mTempBounds.top);
mBadgeRenderer.draw(canvas, mBadgeInfo, mTempBounds, IconPalette badgePalette = IconPalette.getFolderBadgePalette(getResources());
mBadgeRenderer.draw(canvas, badgePalette, mBadgeInfo, mTempBounds,
badgeScale, mTempSpaceForBadgeOffset); badgeScale, mTempSpaceForBadgeOffset);
} }
} }
@@ -18,9 +18,12 @@ package com.android.launcher3.graphics;
import android.app.Notification; import android.app.Notification;
import android.content.Context; import android.content.Context;
import android.content.res.Resources;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.ColorMatrix; import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter; import android.graphics.ColorMatrixColorFilter;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.graphics.ColorUtils; import android.support.v4.graphics.ColorUtils;
import android.util.Log; import android.util.Log;
@@ -35,11 +38,12 @@ public class IconPalette {
private static final boolean DEBUG = false; private static final boolean DEBUG = false;
private static final String TAG = "IconPalette"; private static final String TAG = "IconPalette";
public static final IconPalette FOLDER_ICON_PALETTE = new IconPalette(Color.parseColor("#BDC1C6"));
private static final float MIN_PRELOAD_COLOR_SATURATION = 0.2f; private static final float MIN_PRELOAD_COLOR_SATURATION = 0.2f;
private static final float MIN_PRELOAD_COLOR_LIGHTNESS = 0.6f; private static final float MIN_PRELOAD_COLOR_LIGHTNESS = 0.6f;
private static IconPalette sBadgePalette;
private static IconPalette sFolderBadgePalette;
public final int dominantColor; public final int dominantColor;
public final int backgroundColor; public final int backgroundColor;
public final ColorMatrixColorFilter backgroundColorMatrixFilter; public final ColorMatrixColorFilter backgroundColorMatrixFilter;
@@ -47,15 +51,19 @@ public class IconPalette {
public final int textColor; public final int textColor;
public final int secondaryColor; public final int secondaryColor;
private IconPalette(int color) { private IconPalette(int color, boolean desaturateBackground) {
dominantColor = color; dominantColor = color;
backgroundColor = dominantColor; backgroundColor = desaturateBackground ? getMutedColor(dominantColor, 0.87f) : dominantColor;
ColorMatrix backgroundColorMatrix = new ColorMatrix(); ColorMatrix backgroundColorMatrix = new ColorMatrix();
Themes.setColorScaleOnMatrix(backgroundColor, backgroundColorMatrix); Themes.setColorScaleOnMatrix(backgroundColor, backgroundColorMatrix);
backgroundColorMatrixFilter = new ColorMatrixColorFilter(backgroundColorMatrix); backgroundColorMatrixFilter = new ColorMatrixColorFilter(backgroundColorMatrix);
// Get slightly more saturated background color. if (!desaturateBackground) {
Themes.setColorScaleOnMatrix(getMutedColor(dominantColor, 0.54f), backgroundColorMatrix); saturatedBackgroundColorMatrixFilter = backgroundColorMatrixFilter;
saturatedBackgroundColorMatrixFilter = new ColorMatrixColorFilter(backgroundColorMatrix); } else {
// Get slightly more saturated background color.
Themes.setColorScaleOnMatrix(getMutedColor(dominantColor, 0.54f), backgroundColorMatrix);
saturatedBackgroundColorMatrixFilter = new ColorMatrixColorFilter(backgroundColorMatrix);
}
textColor = getTextColorForBackground(backgroundColor); textColor = getTextColorForBackground(backgroundColor);
secondaryColor = getLowContrastColor(backgroundColor); secondaryColor = getLowContrastColor(backgroundColor);
} }
@@ -78,8 +86,35 @@ public class IconPalette {
return result; return result;
} }
public static IconPalette fromDominantColor(int dominantColor) { public static IconPalette fromDominantColor(int dominantColor, boolean desaturateBackground) {
return new IconPalette(dominantColor); return new IconPalette(dominantColor, desaturateBackground);
}
/**
* Returns an IconPalette based on the badge_color in colors.xml.
* If that color is Color.TRANSPARENT, then returns null instead.
*/
public static @Nullable IconPalette getBadgePalette(Resources resources) {
int badgeColor = resources.getColor(R.color.badge_color);
if (badgeColor == Color.TRANSPARENT) {
// Colors will be extracted per app icon, so a static palette won't work.
return null;
}
if (sBadgePalette == null) {
sBadgePalette = fromDominantColor(badgeColor, false);
}
return sBadgePalette;
}
/**
* Returns an IconPalette based on the folder_badge_color in colors.xml.
*/
public static @NonNull IconPalette getFolderBadgePalette(Resources resources) {
if (sFolderBadgePalette == null) {
int badgeColor = resources.getColor(R.color.folder_badge_color);
sFolderBadgePalette = fromDominantColor(badgeColor, false);
}
return sFolderBadgePalette;
} }
/** /**
@@ -47,7 +47,6 @@ import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BubbleTextView; import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DragSource; import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget; import com.android.launcher3.DropTarget;
import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.ItemInfo; import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher; import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils; import com.android.launcher3.LauncherAnimUtils;
@@ -579,9 +578,7 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra
ItemInfo itemInfo = (ItemInfo) mOriginalIcon.getTag(); ItemInfo itemInfo = (ItemInfo) mOriginalIcon.getTag();
BadgeInfo badgeInfo = mLauncher.getPopupDataProvider().getBadgeInfoForItem(itemInfo); BadgeInfo badgeInfo = mLauncher.getPopupDataProvider().getBadgeInfoForItem(itemInfo);
if (mNotificationItemView != null && badgeInfo != null) { if (mNotificationItemView != null && badgeInfo != null) {
IconPalette palette = mOriginalIcon.getIcon() instanceof FastBitmapDrawable IconPalette palette = mOriginalIcon.getBadgePalette();
? ((FastBitmapDrawable) mOriginalIcon.getIcon()).getIconPalette()
: null;
mNotificationItemView.updateHeader(badgeInfo.getNotificationCount(), palette); mNotificationItemView.updateHeader(badgeInfo.getNotificationCount(), palette);
} }
} }