From: Tony Wickham Date: Thu, 2 Feb 2017 20:57:18 +0000 (-0800) Subject: Animate badges when they are added or removed X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=1e61849bc729858f3fd0c0a1f31b15ef06cffdc3;p=android-x86%2Fpackages-apps-Launcher3.git Animate badges when they are added or removed - Scale the badge and text or icon up or down, respectively. - Only animate if the badge is visible, and don't animate when applying shortcut or app info. - Animate folder badge out when folder enters accepting state. Bug: 34838365 Bug: 32410600 Change-Id: Ie60cb1fc54fe60d72734d833040545d27660d645 --- diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 8043eacdb..bbf2cb8d7 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -167,7 +167,7 @@ public class BubbleTextView extends TextView applyPromiseState(promiseStateChanged); } - applyBadgeState(info); + applyBadgeState(info, false /* animate */); } public void applyFromApplicationInfo(AppInfo info) { @@ -179,7 +179,7 @@ public class BubbleTextView extends TextView // Verify high res immediately verifyHighRes(); - applyBadgeState(info); + applyBadgeState(info, false /* animate */); } public void applyFromPackageItemInfo(PackageItemInfo info) { @@ -501,11 +501,11 @@ public class BubbleTextView extends TextView } } - public void applyBadgeState(ItemInfo itemInfo) { + public void applyBadgeState(ItemInfo itemInfo, boolean animate) { if (mIcon instanceof FastBitmapDrawable) { BadgeInfo badgeInfo = mLauncher.getPopupDataProvider().getBadgeInfoForItem(itemInfo); BadgeRenderer badgeRenderer = mLauncher.getDeviceProfile().mBadgeRenderer; - ((FastBitmapDrawable) mIcon).applyIconBadge(badgeInfo, badgeRenderer); + ((FastBitmapDrawable) mIcon).applyIconBadge(badgeInfo, badgeRenderer, animate); } } diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java index 95d2dafce..1f74c8877 100644 --- a/src/com/android/launcher3/FastBitmapDrawable.java +++ b/src/com/android/launcher3/FastBitmapDrawable.java @@ -30,7 +30,7 @@ import android.graphics.PixelFormat; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; -import android.util.Log; +import android.util.Property; import android.util.SparseArray; import android.view.animation.DecelerateInterpolator; @@ -107,6 +107,21 @@ public class FastBitmapDrawable extends Drawable { private BadgeInfo mBadgeInfo; private BadgeRenderer mBadgeRenderer; private IconPalette mIconPalette; + private float mBadgeScale; + + private static final Property BADGE_SCALE_PROPERTY + = new Property(Float.TYPE, "badgeScale") { + @Override + public Float get(FastBitmapDrawable fastBitmapDrawable) { + return fastBitmapDrawable.mBadgeScale; + } + + @Override + public void set(FastBitmapDrawable fastBitmapDrawable, Float value) { + fastBitmapDrawable.mBadgeScale = value; + fastBitmapDrawable.invalidateSelf(); + } + }; // 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 @@ -123,14 +138,22 @@ public class FastBitmapDrawable extends Drawable { setFilterBitmap(true); } - public void applyIconBadge(BadgeInfo badgeInfo, BadgeRenderer badgeRenderer) { + public void applyIconBadge(final BadgeInfo badgeInfo, BadgeRenderer badgeRenderer, + boolean animate) { boolean wasBadged = mBadgeInfo != null; boolean isBadged = badgeInfo != null; + float newBadgeScale = isBadged ? 1f : 0; mBadgeInfo = badgeInfo; mBadgeRenderer = badgeRenderer; if (wasBadged || isBadged) { mIconPalette = getIconPalette(); - invalidateSelf(); + // Animate when a badge is first added or when it is removed. + if (animate && (wasBadged ^ isBadged) && isVisible()) { + ObjectAnimator.ofFloat(this, BADGE_SCALE_PROPERTY, newBadgeScale).start(); + } else { + mBadgeScale = newBadgeScale; + invalidateSelf(); + } } } @@ -154,7 +177,7 @@ public class FastBitmapDrawable extends Drawable { protected void drawBadgeIfNecessary(Canvas canvas) { if (hasBadge()) { - mBadgeRenderer.draw(canvas, mIconPalette, mBadgeInfo, getBounds()); + mBadgeRenderer.draw(canvas, mIconPalette, mBadgeInfo, getBounds(), mBadgeScale); } } @@ -167,7 +190,7 @@ public class FastBitmapDrawable extends Drawable { } private boolean hasBadge() { - return mBadgeInfo != null && mBadgeInfo.getNotificationCount() != 0; + return (mBadgeInfo != null && mBadgeInfo.getNotificationCount() > 0) || mBadgeScale > 0; } @Override diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index cd74ed96f..3aa882587 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -3993,7 +3993,7 @@ public class Workspace extends PagedView if (info instanceof ShortcutInfo && v instanceof BubbleTextView && packageUserKey.updateFromItemInfo(info)) { if (updatedBadges.contains(packageUserKey)) { - ((BubbleTextView) v).applyBadgeState(info); + ((BubbleTextView) v).applyBadgeState(info, true /* animate */); folderIds.add(info.container); } } diff --git a/src/com/android/launcher3/badge/BadgeRenderer.java b/src/com/android/launcher3/badge/BadgeRenderer.java index 4bb09546f..8bbc2afa2 100644 --- a/src/com/android/launcher3/badge/BadgeRenderer.java +++ b/src/com/android/launcher3/badge/BadgeRenderer.java @@ -24,6 +24,7 @@ import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Shader; +import android.support.annotation.Nullable; import com.android.launcher3.R; import com.android.launcher3.graphics.IconPalette; @@ -34,62 +35,59 @@ import com.android.launcher3.graphics.IconPalette; */ public class BadgeRenderer { - public int size; - public int textSize; - public IconDrawer largeIconDrawer; - public IconDrawer smallIconDrawer; - private final Context mContext; - private final RectF mBackgroundRect = new RectF(); + private final int mSize; + private final int mTextHeight; + private final IconDrawer mLargeIconDrawer; + private final IconDrawer mSmallIconDrawer; 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(Context context) { mContext = context; Resources res = context.getResources(); - size = res.getDimensionPixelSize(R.dimen.badge_size); - textSize = res.getDimensionPixelSize(R.dimen.badge_text_size); - largeIconDrawer = new IconDrawer(res.getDimensionPixelSize(R.dimen.badge_small_padding)); - smallIconDrawer = new IconDrawer(res.getDimensionPixelSize(R.dimen.badge_large_padding)); + mSize = res.getDimensionPixelSize(R.dimen.badge_size); + mLargeIconDrawer = new IconDrawer(res.getDimensionPixelSize(R.dimen.badge_small_padding)); + mSmallIconDrawer = new IconDrawer(res.getDimensionPixelSize(R.dimen.badge_large_padding)); mTextPaint.setTextAlign(Paint.Align.CENTER); - mTextPaint.setTextSize(textSize); + mTextPaint.setTextSize(res.getDimensionPixelSize(R.dimen.badge_text_size)); // Measure the text height. - Rect temp = new Rect(); - mTextPaint.getTextBounds("0", 0, 1, temp); - mTextHeight = temp.height(); + Rect tempTextHeight = new Rect(); + mTextPaint.getTextBounds("0", 0, 1, tempTextHeight); + mTextHeight = tempTextHeight.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 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 badgeScale The progress of the animation, from 0 to 1. */ - public void draw(Canvas canvas, IconPalette palette, BadgeInfo badgeInfo, Rect iconBounds) { + public void draw(Canvas canvas, IconPalette palette, @Nullable BadgeInfo badgeInfo, + Rect iconBounds, float badgeScale) { mBackgroundPaint.setColor(palette.backgroundColor); mTextPaint.setColor(palette.textColor); - mBackgroundRect.set(iconBounds.right - size, iconBounds.top, iconBounds.right, - iconBounds.top + size); - canvas.drawOval(mBackgroundRect, mBackgroundPaint); - IconDrawer iconDrawer = badgeInfo.isIconLarge() ? largeIconDrawer : smallIconDrawer; - Shader icon = badgeInfo.getNotificationIconForBadge(mContext, palette.backgroundColor, size, - iconDrawer.mPadding); + canvas.save(Canvas.MATRIX_SAVE_FLAG); + // We draw the badge relative to its center. + canvas.translate(iconBounds.right - mSize / 2, iconBounds.top + mSize / 2); + canvas.scale(badgeScale, badgeScale); + canvas.drawCircle(0, 0, mSize / 2, mBackgroundPaint); + IconDrawer iconDrawer = badgeInfo != null && badgeInfo.isIconLarge() + ? mLargeIconDrawer : mSmallIconDrawer; + Shader icon = badgeInfo == null ? null : badgeInfo.getNotificationIconForBadge( + mContext, palette.backgroundColor, mSize, iconDrawer.mPadding); if (icon != null) { // Draw the notification icon with padding. - canvas.save(); - canvas.translate(mBackgroundRect.left, mBackgroundRect.top); iconDrawer.drawIcon(icon, canvas); - canvas.restore(); } else { // Draw the notification count. - String notificationCount = String.valueOf(badgeInfo.getNotificationCount()); - canvas.drawText(notificationCount, - mBackgroundRect.centerX(), - mBackgroundRect.centerY() + mTextHeight / 2, - mTextPaint); + String notificationCount = badgeInfo == null ? "0" + : String.valueOf(badgeInfo.getNotificationCount()); + canvas.drawText(notificationCount, 0, mTextHeight / 2, mTextPaint); } + canvas.restore(); } /** Draws the notification icon with padding of a given size. */ @@ -102,15 +100,15 @@ public class BadgeRenderer { public IconDrawer(int padding) { mPadding = padding; - mCircleClipBitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ALPHA_8); + mCircleClipBitmap = Bitmap.createBitmap(mSize, mSize, Bitmap.Config.ALPHA_8); Canvas canvas = new Canvas(); canvas.setBitmap(mCircleClipBitmap); - canvas.drawCircle(size / 2, size / 2, size / 2 - padding, mPaint); + canvas.drawCircle(mSize / 2, mSize / 2, mSize / 2 - padding, mPaint); } public void drawIcon(Shader icon, Canvas canvas) { mPaint.setShader(icon); - canvas.drawBitmap(mCircleClipBitmap, 0f, 0f, mPaint); + canvas.drawBitmap(mCircleClipBitmap, -mSize / 2, -mSize / 2, mPaint); mPaint.setShader(null); } } diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index 74dc48c06..c80255994 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -33,6 +33,7 @@ import android.graphics.drawable.Drawable; import android.os.Parcelable; import android.util.AttributeSet; import android.util.DisplayMetrics; +import android.util.Property; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; @@ -129,6 +130,21 @@ public class FolderIcon extends FrameLayout implements FolderListener { private FolderBadgeInfo mBadgeInfo = new FolderBadgeInfo(); private BadgeRenderer mBadgeRenderer; + private float mBadgeScale; + + private static final Property BADGE_SCALE_PROPERTY + = new Property(Float.TYPE, "badgeScale") { + @Override + public Float get(FolderIcon folderIcon) { + return folderIcon.mBadgeScale; + } + + @Override + public void set(FolderIcon folderIcon, Float value) { + folderIcon.mBadgeScale = value; + folderIcon.invalidate(); + } + }; public FolderIcon(Context context, AttributeSet attrs) { super(context, attrs); @@ -387,8 +403,25 @@ public class FolderIcon extends FrameLayout implements FolderListener { } public void setBadgeInfo(FolderBadgeInfo badgeInfo) { + updateBadgeScale(mBadgeInfo.getNotificationCount(), badgeInfo.getNotificationCount()); mBadgeInfo = badgeInfo; - invalidate(); + } + + /** + * Sets mBadgeScale to 1 or 0, animating if oldCount or newCount is 0 + * (the badge is being added or removed). + */ + private void updateBadgeScale(int oldCount, int newCount) { + boolean wasBadged = oldCount > 0; + boolean isBadged = newCount > 0; + float newBadgeScale = isBadged ? 1f : 0f; + // Animate when a badge is first added or when it is removed. + if ((wasBadged ^ isBadged) && isShown()) { + ObjectAnimator.ofFloat(this, BADGE_SCALE_PROPERTY, newBadgeScale).start(); + } else { + mBadgeScale = newBadgeScale; + invalidate(); + } } static class PreviewItemDrawingParams { @@ -549,6 +582,14 @@ public class FolderIcon extends FrameLayout implements FolderListener { return basePreviewOffsetY - (getScaledRadius() - getRadius()); } + /** + * Returns the progress of the scale animation, where 0 means the scale is at 1f + * and 1 means the scale is at ACCEPT_SCALE_FACTOR. + */ + float getScaleProgress() { + return (mScale - 1f) / (ACCEPT_SCALE_FACTOR - 1f); + } + void invalidate() { int radius = getScaledRadius(); mClipPath.reset(); @@ -784,8 +825,10 @@ public class FolderIcon extends FrameLayout implements FolderListener { int offsetY = mBackground.getOffsetY(); int previewSize = (int) (mBackground.previewSize * mBackground.mScale); Rect bounds = new Rect(offsetX, offsetY, offsetX + previewSize, offsetY + previewSize); - if (mBadgeInfo != null && mBadgeInfo.getNotificationCount() > 0) { - mBadgeRenderer.draw(canvas, IconPalette.FOLDER_ICON_PALETTE, mBadgeInfo, bounds); + if ((mBadgeInfo != null && mBadgeInfo.getNotificationCount() > 0) || mBadgeScale > 0) { + // If we are animating to the accepting state, animate the badge out. + float badgeScale = Math.max(0, mBadgeScale - mBackground.getScaleProgress()); + mBadgeRenderer.draw(canvas, IconPalette.FOLDER_ICON_PALETTE, mBadgeInfo, bounds, badgeScale); } } @@ -938,14 +981,20 @@ public class FolderIcon extends FrameLayout implements FolderListener { @Override public void onAdd(ShortcutInfo item) { + int oldCount = mBadgeInfo.getNotificationCount(); mBadgeInfo.addBadgeInfo(mLauncher.getPopupDataProvider().getBadgeInfoForItem(item)); + int newCount = mBadgeInfo.getNotificationCount(); + updateBadgeScale(oldCount, newCount); invalidate(); requestLayout(); } @Override public void onRemove(ShortcutInfo item) { + int oldCount = mBadgeInfo.getNotificationCount(); mBadgeInfo.subtractBadgeInfo(mLauncher.getPopupDataProvider().getBadgeInfoForItem(item)); + int newCount = mBadgeInfo.getNotificationCount(); + updateBadgeScale(oldCount, newCount); invalidate(); requestLayout(); }