FastBitmapDrawable can draw an icon badge (notification count)

- Added BadgeInfo to contain data to be shown in a badge
  (currently just notification count).
- Added BadgeRenderer in DeviceProfile to contain things
  relevant to drawing the badge, such as size and Paint's.
- Added IconPalette to compute colors for the badge based
  on a dominant color (will also be used for notifications)
- FastBitmapDrawable uses these classes to draw the badge.

Bug: 32410600
Change-Id: I6595a4879943357590f7d20c22594691a573ecaf
This commit is contained in:
Tony Wickham
2017-01-11 09:53:12 -08:00
parent 19ea5cc0c8
commit 9a8d11f930
9 changed files with 223 additions and 7 deletions
+4
View File
@@ -169,6 +169,10 @@
also happens to equal 19dp-->
<dimen name="deep_shortcuts_arrow_horizontal_offset">19dp</dimen>
<!-- Icon badges (with notification counts) -->
<dimen name="badge_size">24dp</dimen>
<dimen name="badge_text_size">12dp</dimen>
<!-- Other -->
<!-- Approximates the system status bar height. Not guaranteed to be always be correct. -->
<dimen name="status_bar_height">24dp</dimen>
+13 -4
View File
@@ -39,6 +39,8 @@ import android.widget.TextView;
import com.android.launcher3.IconCache.IconLoadRequest;
import com.android.launcher3.IconCache.ItemInfoUpdateReceiver;
import com.android.launcher3.badge.BadgeRenderer;
import com.android.launcher3.badge.BadgeInfo;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.graphics.DrawableFactory;
import com.android.launcher3.graphics.HolographicOutlineHelper;
@@ -164,7 +166,7 @@ public class BubbleTextView extends TextView
applyIconAndLabel(info.iconBitmap, info);
setTag(info);
if (promiseStateChanged || info.isPromise()) {
applyState(promiseStateChanged);
applyPromiseState(promiseStateChanged);
}
}
@@ -470,7 +472,7 @@ public class BubbleTextView extends TextView
mLongPressHelper.cancelLongPress();
}
public void applyState(boolean promiseStateChanged) {
public void applyPromiseState(boolean promiseStateChanged) {
if (getTag() instanceof ShortcutInfo) {
ShortcutInfo info = (ShortcutInfo) getTag();
final boolean isPromise = info.isPromise();
@@ -479,8 +481,8 @@ public class BubbleTextView extends TextView
info.getInstallProgress() : 0)) : 100;
setContentDescription(progressLevel > 0 ?
getContext().getString(R.string.app_downloading_title, info.title,
NumberFormat.getPercentInstance().format(progressLevel * 0.01)) :
getContext().getString(R.string.app_downloading_title, info.title,
NumberFormat.getPercentInstance().format(progressLevel * 0.01)) :
getContext().getString(R.string.app_waiting_download_title, info.title));
if (mIcon != null) {
@@ -500,6 +502,13 @@ public class BubbleTextView extends TextView
}
}
public void applyBadgeState(BadgeInfo badgeInfo) {
if (mIcon instanceof FastBitmapDrawable) {
BadgeRenderer badgeRenderer = mLauncher.getDeviceProfile().mBadgeRenderer;
((FastBitmapDrawable) mIcon).applyIconBadge(badgeInfo, badgeRenderer);
}
}
private Theme getPreloaderTheme() {
Object tag = getTag();
int style = ((tag != null) && (tag instanceof ShortcutInfo) &&
+8 -1
View File
@@ -31,7 +31,7 @@ import android.view.ViewGroup.LayoutParams;
import android.widget.FrameLayout;
import com.android.launcher3.CellLayout.ContainerType;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.badge.BadgeRenderer;
import java.util.ArrayList;
@@ -136,6 +136,9 @@ public class DeviceProfile {
// Listeners
private ArrayList<LauncherLayoutChangeListener> mListeners = new ArrayList<>();
// Icon badges
public BadgeRenderer mBadgeRenderer;
public DeviceProfile(Context context, InvariantDeviceProfile inv,
Point minSize, Point maxSize,
int width, int height, boolean isLandscape) {
@@ -193,6 +196,10 @@ public class DeviceProfile {
hotseatBarBottomPaddingPx = 0;
hotseatLandGutterPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_gutter_width);
int badgeSize = res.getDimensionPixelSize(R.dimen.badge_size);
int badgeTextSize = res.getDimensionPixelSize(R.dimen.badge_text_size);
mBadgeRenderer = new BadgeRenderer(badgeSize, badgeTextSize);
// Determine sizes.
widthPx = width;
heightPx = height;
@@ -33,6 +33,10 @@ import android.graphics.drawable.Drawable;
import android.util.SparseArray;
import android.view.animation.DecelerateInterpolator;
import com.android.launcher3.graphics.IconPalette;
import com.android.launcher3.badge.BadgeRenderer;
import com.android.launcher3.badge.BadgeInfo;
public class FastBitmapDrawable extends Drawable {
private static final float DISABLED_DESATURATION = 1f;
private static final float DISABLED_BRIGHTNESS = 0.5f;
@@ -99,6 +103,10 @@ public class FastBitmapDrawable extends Drawable {
private State mState = State.NORMAL;
private boolean mIsDisabled;
private BadgeInfo mBadgeInfo;
private BadgeRenderer mBadgeRenderer;
private IconPalette mIconPalette;
// The saturation and brightness are values that are mapped to REDUCED_FILTER_VALUE_SPACE and
// as a result, can be used to compose the key for the cached ColorMatrixColorFilters
private int mDesaturation = 0;
@@ -114,9 +122,21 @@ public class FastBitmapDrawable extends Drawable {
setBounds(0, 0, b.getWidth(), b.getHeight());
}
public void applyIconBadge(BadgeInfo badgeInfo, BadgeRenderer badgeRenderer) {
mBadgeInfo = badgeInfo;
mBadgeRenderer = badgeRenderer;
if (mIconPalette == null) {
mIconPalette = IconPalette.fromDominantColor(Utilities
.findDominantColorByHue(mBitmap, 20));
}
invalidateSelf();
}
@Override
public void draw(Canvas canvas) {
drawInternal(canvas);
// Draw the icon badge in the top right corner.
drawBadgeIfNecessary(canvas);
}
public void drawWithBrightness(Canvas canvas, float brightness) {
@@ -130,6 +150,16 @@ public class FastBitmapDrawable extends Drawable {
canvas.drawBitmap(mBitmap, null, getBounds(), mPaint);
}
protected void drawBadgeIfNecessary(Canvas canvas) {
if (hasBadge()) {
mBadgeRenderer.draw(canvas, mIconPalette, mBadgeInfo, getBounds());
}
}
private boolean hasBadge() {
return mBadgeInfo != null && mBadgeInfo.getNotificationCount() != null;
}
@Override
public void setColorFilter(ColorFilter cf) {
// No op
+1 -1
View File
@@ -306,7 +306,7 @@ public final class Utilities {
* @param bitmap The bitmap to scan
* @param samples The approximate max number of samples to use.
*/
static int findDominantColorByHue(Bitmap bitmap, int samples) {
public static int findDominantColorByHue(Bitmap bitmap, int samples) {
final int height = bitmap.getHeight();
final int width = bitmap.getWidth();
int sampleStride = (int) Math.sqrt((height * width) / samples);
+1 -1
View File
@@ -3972,7 +3972,7 @@ public class Workspace extends PagedView
public boolean evaluate(ItemInfo info, View v) {
if (info instanceof ShortcutInfo && v instanceof BubbleTextView
&& updates.contains(info)) {
((BubbleTextView) v).applyState(false);
((BubbleTextView) v).applyPromiseState(false /* promiseStateChanged */);
} else if (v instanceof PendingAppWidgetHostView
&& info instanceof LauncherAppWidgetInfo
&& updates.contains(info)) {
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.badge;
/**
* Contains data to be used in an icon badge.
*/
public class BadgeInfo {
private int mNotificationCount;
public void setNotificationCount(int count) {
mNotificationCount = count;
}
public String getNotificationCount() {
return mNotificationCount == 0 ? null : String.valueOf(mNotificationCount);
}
}
@@ -0,0 +1,70 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.badge;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import com.android.launcher3.graphics.IconPalette;
/**
* Contains parameters necessary to draw a badge for an icon (e.g. the size of the badge).
* @see BadgeInfo for the data to draw
*/
public class BadgeRenderer {
public int size;
public int textSize;
private final RectF mBackgroundRect = new RectF();
private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final int mTextHeight;
public BadgeRenderer(int size, int textSize) {
this.size = size;
this.textSize = textSize;
mTextPaint.setTextAlign(Paint.Align.CENTER);
mTextPaint.setTextSize(textSize);
// Measure the text height.
Rect temp = new Rect();
mTextPaint.getTextBounds("0", 0, 1, temp);
mTextHeight = temp.height();
}
/**
* Draw a circle in the top right corner of the given bounds, and draw
* {@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.
* @param iconBounds The bounds of the icon being badged.
*/
public void draw(Canvas canvas, IconPalette palette, BadgeInfo badgeInfo, Rect iconBounds) {
mBackgroundPaint.setColor(palette.backgroundColor);
mTextPaint.setColor(palette.textColor);
mBackgroundRect.set(iconBounds.right - size, iconBounds.top, iconBounds.right,
iconBounds.top + size);
canvas.drawOval(mBackgroundRect, mBackgroundPaint);
String notificationCount = badgeInfo.getNotificationCount();
canvas.drawText(notificationCount,
mBackgroundRect.centerX(),
mBackgroundRect.centerY() + mTextHeight / 2,
mTextPaint);
}
}
@@ -0,0 +1,63 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.graphics;
import android.graphics.Color;
import android.support.v4.graphics.ColorUtils;
/**
* Contains colors based on the dominant color of an icon.
*/
public class IconPalette {
public int backgroundColor;
public int textColor;
public static IconPalette fromDominantColor(int dominantColor) {
IconPalette palette = new IconPalette();
palette.backgroundColor = getMutedColor(dominantColor);
palette.textColor = getTextColorForBackground(palette.backgroundColor);
return palette;
}
private static int getMutedColor(int color) {
int alpha = (int) (255 * 0.2f);
return ColorUtils.compositeColors(ColorUtils.setAlphaComponent(color, alpha), Color.WHITE);
}
private static int getTextColorForBackground(int backgroundColor) {
return getLighterOrDarkerVersionOfColor(backgroundColor, 3f);
}
private static int getLowContrastColor(int color) {
return getLighterOrDarkerVersionOfColor(color, 1.5f);
}
private static int getLighterOrDarkerVersionOfColor(int color, float contrastRatio) {
int whiteMinAlpha = ColorUtils.calculateMinimumAlpha(Color.WHITE, color, contrastRatio);
int blackMinAlpha = ColorUtils.calculateMinimumAlpha(Color.BLACK, color, contrastRatio);
int translucentWhiteOrBlack;
if (whiteMinAlpha >= 0) {
translucentWhiteOrBlack = ColorUtils.setAlphaComponent(Color.WHITE, whiteMinAlpha);
} else if (blackMinAlpha >= 0) {
translucentWhiteOrBlack = ColorUtils.setAlphaComponent(Color.BLACK, blackMinAlpha);
} else {
translucentWhiteOrBlack = Color.WHITE;
}
return ColorUtils.compositeColors(translucentWhiteOrBlack, color);
}
}