Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1e61849b authored by Tony Wickham's avatar Tony Wickham
Browse files

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
parent 00002d02
Loading
Loading
Loading
Loading
+4 −4
Original line number Diff line number Diff line
@@ -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);
        }
    }

+28 −5
Original line number Diff line number Diff line
@@ -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<FastBitmapDrawable, Float> BADGE_SCALE_PROPERTY
            = new Property<FastBitmapDrawable, Float>(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,16 +138,24 @@ 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();
            // 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();
            }
        }
    }

    @Override
    public void draw(Canvas canvas) {
@@ -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
+1 −1
Original line number Diff line number Diff line
@@ -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);
                    }
                }
+32 −34
Original line number Diff line number Diff line
@@ -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);
        }
    }
+52 −3
Original line number Diff line number Diff line
@@ -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<FolderIcon, Float> BADGE_SCALE_PROPERTY
            = new Property<FolderIcon, Float>(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,9 +403,26 @@ public class FolderIcon extends FrameLayout implements FolderListener {
    }

    public void setBadgeInfo(FolderBadgeInfo badgeInfo) {
        updateBadgeScale(mBadgeInfo.getNotificationCount(), badgeInfo.getNotificationCount());
        mBadgeInfo = badgeInfo;
    }

    /**
     * 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 {
        PreviewItemDrawingParams(float transX, float transY, float scale, float overlayAlpha) {
@@ -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();
    }