From: Sunny Goyal Date: Fri, 15 May 2015 02:55:10 +0000 (-0700) Subject: Optimizing shadow generation by reusing bitmap. X-Git-Tag: android-x86-7.1-r1~166^2~474^2 X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=4fe5a37dda8eb1869f9328d94236eb2c23f8109c;p=android-x86%2Fpackages-apps-Launcher3.git Optimizing shadow generation by reusing bitmap. > Not creating unnecessary bitmaps > Final bitmap is generated as ALPHA_8 instead of ARGB_8888 > The shadow drawing is done directly in the view Change-Id: I504fa2ea3abdc1a3c3fb9ad57d6e28880d2584a1 --- diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 5e1f3de5c..d2f6237a8 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -119,4 +119,11 @@ 24dp 4dp 2dp + + + 1dp + 2dp + 4dp + 2dp + diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index bbcd893f2..5dc3b1239 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -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(); diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index 72eabf177..a348008d8 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -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 mReorderAnimators = new HashMap(); @@ -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,13 +913,13 @@ 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, - left + r - l, - top + b - t); - } + + mTouchFeedbackView.layout(left, top, + left + mTouchFeedbackView.getMeasuredWidth(), + top + mTouchFeedbackView.getMeasuredHeight()); + mShortcutsAndWidgets.layout(left, top, + left + r - l, + top + b - t); } @Override diff --git a/src/com/android/launcher3/ClickShadowView.java b/src/com/android/launcher3/ClickShadowView.java new file mode 100644 index 000000000..42fafe275 --- /dev/null +++ b/src/com/android/launcher3/ClickShadowView.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2014 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; + +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 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 Bitmap mBitmap; + + 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); + } + + /** + * Applies the new bitmap. + * @return true if the view was invalidated. + */ + public boolean setBitmap(Bitmap b) { + if (b != mBitmap){ + mBitmap = b; + invalidate(); + return true; + } + return false; + } + + @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 */ + ); + } +} diff --git a/src/com/android/launcher3/FastBitmapView.java b/src/com/android/launcher3/FastBitmapView.java deleted file mode 100644 index 0937eb75e..000000000 --- a/src/com/android/launcher3/FastBitmapView.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2014 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; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.view.View; - -public class FastBitmapView extends View { - - private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG); - private Bitmap mBitmap; - - public FastBitmapView(Context context) { - super(context); - } - - /** - * Applies the new bitmap. - * @return true if the view was invalidated. - */ - 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()); - } - return true; - } - return false; - } - - @Override - protected void onDraw(Canvas canvas) { - if (mBitmap != null) { - canvas.drawBitmap(mBitmap, 0, 0, mPaint); - } - } -} diff --git a/src/com/android/launcher3/HolographicOutlineHelper.java b/src/com/android/launcher3/HolographicOutlineHelper.java index b1e0e68a4..4a04d038a 100644 --- a/src/com/android/launcher3/HolographicOutlineHelper.java +++ b/src/com/android/launcher3/HolographicOutlineHelper.java @@ -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 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(); - 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); + 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); + } - 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); } }