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:
@@ -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>
|
||||
|
||||
@@ -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) &&
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user