OSDN Git Service

FastBitmapDrawable can draw an icon badge (notification count)
authorTony Wickham <twickham@google.com>
Wed, 11 Jan 2017 17:53:12 +0000 (09:53 -0800)
committerTony Wickham <twickham@google.com>
Fri, 13 Jan 2017 18:58:10 +0000 (10:58 -0800)
- 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

res/values/dimens.xml
src/com/android/launcher3/BubbleTextView.java
src/com/android/launcher3/DeviceProfile.java
src/com/android/launcher3/FastBitmapDrawable.java
src/com/android/launcher3/Utilities.java
src/com/android/launcher3/Workspace.java
src/com/android/launcher3/badge/BadgeInfo.java [new file with mode: 0644]
src/com/android/launcher3/badge/BadgeRenderer.java [new file with mode: 0644]
src/com/android/launcher3/graphics/IconPalette.java [new file with mode: 0644]

index 1a09fa0..c7bc286 100644 (file)
          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>
index b8b43c9..77572c7 100644 (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) &&
index 27afdc0..34ce923 100644 (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;
index 0cefc57..b3e59f9 100644 (file)
@@ -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
index 89ffd31..8ad0144 100644 (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);
index cf6b025..1ba6520 100644 (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)) {
diff --git a/src/com/android/launcher3/badge/BadgeInfo.java b/src/com/android/launcher3/badge/BadgeInfo.java
new file mode 100644 (file)
index 0000000..0a9f87c
--- /dev/null
@@ -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);
+    }
+}
diff --git a/src/com/android/launcher3/badge/BadgeRenderer.java b/src/com/android/launcher3/badge/BadgeRenderer.java
new file mode 100644 (file)
index 0000000..238b918
--- /dev/null
@@ -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);
+    }
+}
diff --git a/src/com/android/launcher3/graphics/IconPalette.java b/src/com/android/launcher3/graphics/IconPalette.java
new file mode 100644 (file)
index 0000000..dcc5fcb
--- /dev/null
@@ -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);
+    }
+}