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

Commit 31201109 authored by Sunny Goyal's avatar Sunny Goyal Committed by Android (Google) Code Review
Browse files

Merge "Optimizing shadow generation by reusing bitmap." into ub-launcher3-burnaby

parents 78b19667 4fe5a37d
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -119,4 +119,11 @@
    <dimen name="profile_badge_size">24dp</dimen>
    <dimen name="profile_badge_margin">4dp</dimen>
    <dimen name="profile_badge_minimum_top">2dp</dimen>

<!-- Shadows and outlines -->
    <dimen name="blur_size_thin_outline">1dp</dimen>
    <dimen name="blur_size_medium_outline">2dp</dimen>
    <dimen name="blur_size_click_shadow">4dp</dimen>
    <dimen name="click_shadow_high_shift">2dp</dimen>

</resources>
+1 −1
Original line number Diff line number Diff line
@@ -276,7 +276,7 @@ public class BubbleTextView extends TextView {
        // Only show the shadow effect when persistent pressed state is set.
        if (getParent() instanceof ShortcutAndWidgetContainer) {
            CellLayout layout = (CellLayout) getParent().getParent();
            layout.setPressedIcon(this, mPressedBackground, mOutlineHelper.shadowBitmapPadding);
            layout.setPressedIcon(this, mPressedBackground);
        }

        updateIconState();
+27 −35
Original line number Diff line number Diff line
@@ -132,7 +132,7 @@ public class CellLayout extends ViewGroup {
    private int mDragOutlineCurrent = 0;
    private final Paint mDragOutlinePaint = new Paint();

    private final FastBitmapView mTouchFeedbackView;
    private final ClickShadowView mTouchFeedbackView;

    @Thunk HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new
            HashMap<CellLayout.LayoutParams, Animator>();
@@ -301,9 +301,8 @@ public class CellLayout extends ViewGroup {
        mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
                mCountX, mCountY);

        mTouchFeedbackView = new FastBitmapView(context);
        // Make the feedback view large enough to hold the blur bitmap.
        addView(mTouchFeedbackView, (int) (grid.cellWidthPx * 1.5), (int) (grid.cellHeightPx * 1.5));
        mTouchFeedbackView = new ClickShadowView(context);
        addView(mTouchFeedbackView);
        addView(mShortcutsAndWidgets);
    }

@@ -410,22 +409,14 @@ public class CellLayout extends ViewGroup {
        invalidate();
    }

    void setPressedIcon(BubbleTextView icon, Bitmap background, int padding) {
    void setPressedIcon(BubbleTextView icon, Bitmap background) {
        if (icon == null || background == null) {
            mTouchFeedbackView.setBitmap(null);
            mTouchFeedbackView.animate().cancel();
        } else {
            int offset = getMeasuredWidth() - getPaddingLeft() - getPaddingRight()
                    - (mCountX * mCellWidth);
            mTouchFeedbackView.setTranslationX(icon.getLeft() + (int) Math.ceil(offset / 2f)
                    - padding);
            mTouchFeedbackView.setTranslationY(icon.getTop() - padding);
            if (mTouchFeedbackView.setBitmap(background)) {
                mTouchFeedbackView.setAlpha(0);
                mTouchFeedbackView.animate().alpha(1)
                    .setDuration(FastBitmapDrawable.CLICK_FEEDBACK_DURATION)
                    .setInterpolator(FastBitmapDrawable.CLICK_FEEDBACK_INTERPOLATOR)
                    .start();
                mTouchFeedbackView.alignWithIconView(icon, mShortcutsAndWidgets);
                mTouchFeedbackView.animateShadow();
            }
        }
    }
@@ -895,19 +886,20 @@ public class CellLayout extends ViewGroup {
            mWidthGap = mOriginalWidthGap;
            mHeightGap = mOriginalHeightGap;
        }
        int count = getChildCount();
        int maxWidth = 0;
        int maxHeight = 0;
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(newWidth,
                    MeasureSpec.EXACTLY);
            int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight,
                    MeasureSpec.EXACTLY);
            child.measure(childWidthMeasureSpec, childheightMeasureSpec);
            maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
            maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
        }

        // Make the feedback view large enough to hold the blur bitmap.
        mTouchFeedbackView.measure(
                MeasureSpec.makeMeasureSpec(mCellWidth + mTouchFeedbackView.getExtraSize(),
                        MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(mCellHeight + mTouchFeedbackView.getExtraSize(),
                        MeasureSpec.EXACTLY));

        mShortcutsAndWidgets.measure(
                MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY));

        int maxWidth = mShortcutsAndWidgets.getMeasuredWidth();
        int maxHeight = mShortcutsAndWidgets.getMeasuredHeight();
        if (mFixedWidth > 0 && mFixedHeight > 0) {
            setMeasuredDimension(maxWidth, maxHeight);
        } else {
@@ -921,14 +913,14 @@ public class CellLayout extends ViewGroup {
                (mCountX * mCellWidth);
        int left = getPaddingLeft() + (int) Math.ceil(offset / 2f);
        int top = getPaddingTop();
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            child.layout(left, top,

        mTouchFeedbackView.layout(left, top,
                left + mTouchFeedbackView.getMeasuredWidth(),
                top + mTouchFeedbackView.getMeasuredHeight());
        mShortcutsAndWidgets.layout(left, top,
                left + r - l,
                top + b - t);
    }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+110 −0
Original line number Diff line number Diff line
@@ -19,16 +19,38 @@ package com.android.launcher3;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.View;
import android.view.ViewGroup;

public class FastBitmapView extends View {
public class ClickShadowView extends View {

    private static final int SHADOW_SIZE_FACTOR = 3;
    private static final int SHADOW_LOW_ALPHA = 30;
    private static final int SHADOW_HIGH_ALPHA = 60;

    private final Paint mPaint;

    private final float mShadowOffset;
    private final float mShadowPadding;

    private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
    private Bitmap mBitmap;

    public FastBitmapView(Context context) {
    public ClickShadowView(Context context) {
        super(context);
        mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
        mPaint.setColor(Color.BLACK);

        mShadowPadding = getResources().getDimension(R.dimen.blur_size_click_shadow);
        mShadowOffset = getResources().getDimension(R.dimen.click_shadow_high_shift);
    }

    /**
     * @return extra space required by the view to show the shadow.
     */
    public int getExtraSize() {
        return (int) (SHADOW_SIZE_FACTOR * mShadowPadding);
    }

    /**
@@ -37,13 +59,8 @@ public class FastBitmapView extends View {
     */
    public boolean setBitmap(Bitmap b) {
        if (b != mBitmap){
            if (mBitmap != null) {
                invalidate(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
            }
            mBitmap = b;
            if (mBitmap != null) {
                invalidate(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
            }
            invalidate();
            return true;
        }
        return false;
@@ -52,7 +69,42 @@ public class FastBitmapView extends View {
    @Override
    protected void onDraw(Canvas canvas) {
        if (mBitmap != null) {
            mPaint.setAlpha(SHADOW_LOW_ALPHA);
            canvas.drawBitmap(mBitmap, 0, 0, mPaint);
            mPaint.setAlpha(SHADOW_HIGH_ALPHA);
            canvas.drawBitmap(mBitmap, 0, mShadowOffset, mPaint);
        }
    }

    public void animateShadow() {
        setAlpha(0);
        animate().alpha(1)
            .setDuration(FastBitmapDrawable.CLICK_FEEDBACK_DURATION)
            .setInterpolator(FastBitmapDrawable.CLICK_FEEDBACK_INTERPOLATOR)
            .start();
    }

    /**
     * Aligns the shadow with {@param view}
     * @param viewParent immediate parent of {@param view}. It must be a sibling of this view.
     */
    public void alignWithIconView(BubbleTextView view, ViewGroup viewParent) {
        float leftShift = view.getLeft() + viewParent.getLeft() - getLeft();
        float topShift = view.getTop() + viewParent.getTop() - getTop();
        int iconWidth = view.getRight() - view.getLeft();
        int iconHSpace = iconWidth - view.getCompoundPaddingRight() - view.getCompoundPaddingLeft();
        float drawableWidth = view.getIcon().getBounds().width();

        setTranslationX(leftShift
                + view.getCompoundPaddingLeft() * view.getScaleX()
                + (iconHSpace - drawableWidth) * view.getScaleX() / 2  /* drawable gap */
                + iconWidth * (1 - view.getScaleX()) / 2  /* gap due to scale */
                - mShadowPadding  /* extra shadow size */
                );
        setTranslationY(topShift
                + view.getPaddingTop() * view.getScaleY()  /* drawable gap */
                + view.getHeight() * (1 - view.getScaleY()) / 2  /* gap due to scale */
                - mShadowPadding  /* extra shadow size */
                );
    }
}
+45 −62
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.launcher3;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
@@ -25,11 +26,16 @@ import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.Region.Op;
import android.graphics.drawable.Drawable;
import android.util.SparseArray;

/**
 * Utility class to generate shadow and outline effect, which are used for click feedback
 * and drag-n-drop respectively.
 */
public class HolographicOutlineHelper {

    private static final Rect sTempRect = new Rect();
    private static HolographicOutlineHelper sInstance;

    private final Canvas mCanvas = new Canvas();
    private final Paint mDrawPaint = new Paint();
@@ -40,26 +46,23 @@ public class HolographicOutlineHelper {
    private final BlurMaskFilter mThinOuterBlurMaskFilter;
    private final BlurMaskFilter mMediumInnerBlurMaskFilter;

    private final BlurMaskFilter mShaowBlurMaskFilter;
    private final int mShadowOffset;

    /**
     * Padding used when creating shadow bitmap;
     */
    final int shadowBitmapPadding;
    private final BlurMaskFilter mShadowBlurMaskFilter;

    static HolographicOutlineHelper INSTANCE;
    // We have 4 different icon sizes: homescreen, hotseat, folder & all-apps
    private final SparseArray<Bitmap> mBitmapCache = new SparseArray<>(4);

    private HolographicOutlineHelper(Context context) {
        final float scale = LauncherAppState.getInstance().getScreenDensity();
        Resources res = context.getResources();

        float mediumBlur = res.getDimension(R.dimen.blur_size_medium_outline);
        mMediumOuterBlurMaskFilter = new BlurMaskFilter(mediumBlur, BlurMaskFilter.Blur.OUTER);
        mMediumInnerBlurMaskFilter = new BlurMaskFilter(mediumBlur, BlurMaskFilter.Blur.NORMAL);

        mMediumOuterBlurMaskFilter = new BlurMaskFilter(scale * 2.0f, BlurMaskFilter.Blur.OUTER);
        mThinOuterBlurMaskFilter = new BlurMaskFilter(scale * 1.0f, BlurMaskFilter.Blur.OUTER);
        mMediumInnerBlurMaskFilter = new BlurMaskFilter(scale * 2.0f, BlurMaskFilter.Blur.NORMAL);
        mThinOuterBlurMaskFilter = new BlurMaskFilter(
                res.getDimension(R.dimen.blur_size_thin_outline), BlurMaskFilter.Blur.OUTER);

        mShaowBlurMaskFilter = new BlurMaskFilter(scale * 4.0f, BlurMaskFilter.Blur.NORMAL);
        mShadowOffset = (int) (scale * 2.0f);
        shadowBitmapPadding = (int) (scale * 4.0f);
        mShadowBlurMaskFilter = new BlurMaskFilter(
                res.getDimension(R.dimen.blur_size_click_shadow), BlurMaskFilter.Blur.NORMAL);

        mDrawPaint.setFilterBitmap(true);
        mDrawPaint.setAntiAlias(true);
@@ -71,10 +74,10 @@ public class HolographicOutlineHelper {
    }

    public static HolographicOutlineHelper obtain(Context context) {
        if (INSTANCE == null) {
            INSTANCE = new HolographicOutlineHelper(context);
        if (sInstance == null) {
            sInstance = new HolographicOutlineHelper(context);
        }
        return INSTANCE;
        return sInstance;
    }

    /**
@@ -153,51 +156,31 @@ public class HolographicOutlineHelper {
    }

    Bitmap createMediumDropShadow(BubbleTextView view) {
        final Bitmap result = Bitmap.createBitmap(
                view.getWidth() + shadowBitmapPadding + shadowBitmapPadding,
                view.getHeight() + shadowBitmapPadding + shadowBitmapPadding + mShadowOffset,
                Bitmap.Config.ARGB_8888);

        mCanvas.setBitmap(result);

        final Rect clipRect = sTempRect;
        view.getDrawingRect(sTempRect);
        // adjust the clip rect so that we don't include the text label
        clipRect.bottom = view.getExtendedPaddingTop() - (int) BubbleTextView.PADDING_V
                + view.getLayout().getLineTop(0);

        // Draw the View into the bitmap.
        // The translate of scrollX and scrollY is necessary when drawing TextViews, because
        // they set scrollX and scrollY to large values to achieve centered text
        mCanvas.save();
        mCanvas.scale(view.getScaleX(), view.getScaleY(),
                view.getWidth() / 2 + shadowBitmapPadding,
                view.getHeight() / 2 + shadowBitmapPadding);
        mCanvas.translate(-view.getScrollX() + shadowBitmapPadding,
                -view.getScrollY() + shadowBitmapPadding);
        mCanvas.clipRect(clipRect, Op.REPLACE);
        view.draw(mCanvas);
        mCanvas.restore();

        int[] blurOffst = new int[2];
        mBlurPaint.setMaskFilter(mShaowBlurMaskFilter);
        Bitmap blurBitmap = result.extractAlpha(mBlurPaint, blurOffst);

        mCanvas.save();
        Drawable icon = view.getIcon();
        Rect rect = icon.getBounds();

        int bitmapWidth = (int) (rect.width() * view.getScaleX());
        int bitmapHeight = (int) (rect.height() * view.getScaleY());

        int key = (bitmapWidth << 16) | bitmapHeight;
        Bitmap cache = mBitmapCache.get(key);
        if (cache == null) {
            cache = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
            mCanvas.setBitmap(cache);
            mBitmapCache.put(key, cache);
        } else {
            mCanvas.setBitmap(cache);
            mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
        mCanvas.translate(blurOffst[0], blurOffst[1]);

        mDrawPaint.setColor(Color.BLACK);
        mDrawPaint.setAlpha(30);
        mCanvas.drawBitmap(blurBitmap, 0, 0, mDrawPaint);
        }

        mDrawPaint.setAlpha(60);
        mCanvas.drawBitmap(blurBitmap, 0, mShadowOffset, mDrawPaint);
        mCanvas.save(Canvas.MATRIX_SAVE_FLAG);
        mCanvas.scale(view.getScaleX(), view.getScaleY());
        mCanvas.translate(-rect.left, -rect.top);
        icon.draw(mCanvas);
        mCanvas.restore();

        mCanvas.setBitmap(null);
        blurBitmap.recycle();

        return result;
        mBlurPaint.setMaskFilter(mShadowBlurMaskFilter);
        return cache.extractAlpha(mBlurPaint, null);
    }
}
Loading