diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index e0fcbf04d5..8179dad29a 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -105,7 +105,6 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { private ArrayList mFolderBackgrounds = new ArrayList(); FolderIcon.PreviewBackground mFolderLeaveBehind = new FolderIcon.PreviewBackground(); - Paint mFolderBgPaint = new Paint(); private float mBackgroundAlpha; @@ -501,9 +500,9 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { cellToPoint(bg.delegateCellX, bg.delegateCellY, mTempLocation); canvas.save(); canvas.translate(mTempLocation[0], mTempLocation[1]); - bg.drawBackground(canvas, mFolderBgPaint); + bg.drawBackground(canvas); if (!bg.isClipping) { - bg.drawBackgroundStroke(canvas, mFolderBgPaint); + bg.drawBackgroundStroke(canvas); } canvas.restore(); } @@ -513,7 +512,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { mFolderLeaveBehind.delegateCellY, mTempLocation); canvas.save(); canvas.translate(mTempLocation[0], mTempLocation[1]); - mFolderLeaveBehind.drawLeaveBehind(canvas, mFolderBgPaint); + mFolderLeaveBehind.drawLeaveBehind(canvas); canvas.restore(); } } @@ -528,7 +527,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { cellToPoint(bg.delegateCellX, bg.delegateCellY, mTempLocation); canvas.save(); canvas.translate(mTempLocation[0], mTempLocation[1]); - bg.drawBackgroundStroke(canvas, mFolderBgPaint); + bg.drawBackgroundStroke(canvas); canvas.restore(); } } diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index d84a9d2e3f..92577b7e3a 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -24,11 +24,15 @@ import android.animation.ValueAnimator.AnimatorUpdateListener; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.RadialGradient; import android.graphics.Rect; import android.graphics.Region; +import android.graphics.Shader; import android.graphics.drawable.Drawable; import android.os.Parcelable; import android.util.AttributeSet; @@ -126,8 +130,6 @@ public class FolderIcon extends FrameLayout implements FolderListener { private ArrayList mDrawingParams = new ArrayList(); private Drawable mReferenceDrawable = null; - Paint mBgPaint = new Paint(); - private Alarm mOpenAlarm = new Alarm(); private FolderBadgeInfo mBadgeInfo = new FolderBadgeInfo(); @@ -180,11 +182,6 @@ public class FolderIcon extends FrameLayout implements FolderListener { DeviceProfile grid = launcher.getDeviceProfile(); FolderIcon icon = (FolderIcon) LayoutInflater.from(launcher).inflate(resId, group, false); - // For performance and compatibility reasons we render the preview using a software layer. - // In particular, hardware path clipping has spotty ecosystem support and bad performance. - // Software rendering also allows us to use shadow layers. - icon.setLayerType(LAYER_TYPE_SOFTWARE, new Paint(Paint.FILTER_BITMAP_FLAG)); - icon.setClipToPadding(false); icon.mFolderName = (BubbleTextView) icon.findViewById(R.id.folder_icon_name); icon.mFolderName.setText(folderInfo.title); @@ -495,7 +492,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { } private void drawPreviewItem(Canvas canvas, PreviewItemDrawingParams params) { - canvas.save(); + canvas.save(Canvas.MATRIX_SAVE_FLAG); canvas.translate(params.transX, params.transY); canvas.scale(params.scale, params.scale); Drawable d = params.drawable; @@ -522,10 +519,29 @@ public class FolderIcon extends FrameLayout implements FolderListener { * information, handles drawing, and animation (accept state <--> rest state). */ public static class PreviewBackground { + + private final PorterDuffXfermode mClipPorterDuffXfermode + = new PorterDuffXfermode(PorterDuff.Mode.DST_IN); + // Create a RadialGradient such that it draws a black circle and then extends with + // transparent. To achieve this, we keep the gradient to black for the range [0, 1) and + // just at the edge quickly change it to transparent. + private final RadialGradient mClipShader = new RadialGradient(0, 0, 1, + new int[] {Color.BLACK, Color.BLACK, Color.TRANSPARENT }, + new float[] {0, 0.999f, 1}, + Shader.TileMode.CLAMP); + + private final PorterDuffXfermode mShadowPorterDuffXfermode + = new PorterDuffXfermode(PorterDuff.Mode.DST_OUT); + private RadialGradient mShadowShader = null; + + private final Matrix mShaderMatrix = new Matrix(); + private final Path mPath = new Path(); + + private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private float mScale = 1f; private float mColorMultiplier = 1f; - private Path mClipPath = new Path(); - private int mStrokeWidth; + private float mStrokeWidth; private View mInvalidateDelegate; public int previewSize; @@ -548,7 +564,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { private static final int BG_OPACITY = 160; private static final int MAX_BG_OPACITY = 225; private static final int BG_INTENSITY = 245; - private static final int SHADOW_OPACITY = 80; + private static final int SHADOW_OPACITY = 40; ValueAnimator mScaleAnimator; @@ -564,7 +580,16 @@ public class FolderIcon extends FrameLayout implements FolderListener { basePreviewOffsetX = (availableSpace - this.previewSize) / 2; basePreviewOffsetY = previewPadding + grid.folderBackgroundOffset + topPadding; - mStrokeWidth = Utilities.pxFromDp(1, dm); + // Stroke width is 1dp + mStrokeWidth = dm.density; + + float radius = getScaledRadius(); + float shadowRadius = radius + mStrokeWidth; + int shadowColor = Color.argb(SHADOW_OPACITY, 0, 0, 0); + mShadowShader = new RadialGradient(0, 0, 1, + new int[] {shadowColor, Color.TRANSPARENT}, + new float[] {radius / shadowRadius, 1}, + Shader.TileMode.CLAMP); invalidate(); } @@ -594,10 +619,6 @@ public class FolderIcon extends FrameLayout implements FolderListener { } void invalidate() { - int radius = getScaledRadius(); - mClipPath.reset(); - mClipPath.addCircle(radius, radius, radius, Path.Direction.CW); - if (mInvalidateDelegate != null) { mInvalidateDelegate.invalidate(); } @@ -612,70 +633,94 @@ public class FolderIcon extends FrameLayout implements FolderListener { invalidate(); } - public void drawBackground(Canvas canvas, Paint paint) { - canvas.save(); - canvas.translate(getOffsetX(), getOffsetY()); - - paint.reset(); - paint.setStyle(Paint.Style.FILL); - paint.setXfermode(null); - paint.setAntiAlias(true); - + public void drawBackground(Canvas canvas) { + mPaint.setStyle(Paint.Style.FILL); int alpha = (int) Math.min(MAX_BG_OPACITY, BG_OPACITY * mColorMultiplier); - paint.setColor(Color.argb(alpha, BG_INTENSITY, BG_INTENSITY, BG_INTENSITY)); + mPaint.setColor(Color.argb(alpha, BG_INTENSITY, BG_INTENSITY, BG_INTENSITY)); + drawCircle(canvas, 0 /* deltaRadius */); + + // Draw shadow. + if (mShadowShader == null) { + return; + } float radius = getScaledRadius(); + float shadowRadius = radius + mStrokeWidth; + mPaint.setColor(Color.BLACK); + int offsetX = getOffsetX(); + int offsetY = getOffsetY(); + final int saveCount; + if (canvas.isHardwareAccelerated()) { + saveCount = canvas.saveLayer(offsetX - mStrokeWidth, offsetY, + offsetX + radius + shadowRadius, offsetY + shadowRadius + shadowRadius, + null, Canvas.CLIP_TO_LAYER_SAVE_FLAG | Canvas.HAS_ALPHA_LAYER_SAVE_FLAG); - canvas.drawCircle(radius, radius, radius, paint); - canvas.clipPath(mClipPath, Region.Op.DIFFERENCE); + } else { + saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG); + clipCanvasSoftware(canvas, Region.Op.DIFFERENCE); + } - paint.setStyle(Paint.Style.STROKE); - paint.setColor(Color.TRANSPARENT); - paint.setShadowLayer(mStrokeWidth, 0, mStrokeWidth, Color.argb(SHADOW_OPACITY, 0, 0, 0)); - canvas.drawCircle(radius, radius, radius, paint); + mShaderMatrix.setScale(shadowRadius, shadowRadius); + mShaderMatrix.postTranslate(radius + offsetX, shadowRadius + offsetY); + mShadowShader.setLocalMatrix(mShaderMatrix); + mPaint.setShader(mShadowShader); + canvas.drawPaint(mPaint); + mPaint.setShader(null); - canvas.restore(); + if (canvas.isHardwareAccelerated()) { + mPaint.setXfermode(mShadowPorterDuffXfermode); + canvas.drawCircle(radius + offsetX, radius + offsetY, radius, mPaint); + mPaint.setXfermode(null); + } + + canvas.restoreToCount(saveCount); } - public void drawBackgroundStroke(Canvas canvas, Paint paint) { - canvas.save(); - canvas.translate(getOffsetX(), getOffsetY()); - - paint.reset(); - paint.setAntiAlias(true); - paint.setColor(Color.argb(255, BG_INTENSITY, BG_INTENSITY, BG_INTENSITY)); - paint.setStyle(Paint.Style.STROKE); - paint.setStrokeWidth(mStrokeWidth); - - float radius = getScaledRadius(); - canvas.drawCircle(radius, radius, radius - 1, paint); - - canvas.restore(); + public void drawBackgroundStroke(Canvas canvas) { + mPaint.setColor(Color.argb(255, BG_INTENSITY, BG_INTENSITY, BG_INTENSITY)); + mPaint.setStyle(Paint.Style.STROKE); + mPaint.setStrokeWidth(mStrokeWidth); + drawCircle(canvas, 1 /* deltaRadius */); } - public void drawLeaveBehind(Canvas canvas, Paint paint) { + public void drawLeaveBehind(Canvas canvas) { float originalScale = mScale; mScale = 0.5f; - canvas.save(); - canvas.translate(getOffsetX(), getOffsetY()); + mPaint.setStyle(Paint.Style.FILL); + mPaint.setColor(Color.argb(160, 245, 245, 245)); + drawCircle(canvas, 0 /* deltaRadius */); - paint.reset(); - paint.setAntiAlias(true); - paint.setColor(Color.argb(160, 245, 245, 245)); - - float radius = getScaledRadius(); - canvas.drawCircle(radius, radius, radius, paint); - - canvas.restore(); mScale = originalScale; } - // It is the callers responsibility to save and restore the canvas. - private void clipCanvas(Canvas canvas) { - canvas.translate(getOffsetX(), getOffsetY()); - canvas.clipPath(mClipPath); - canvas.translate(-getOffsetX(), -getOffsetY()); + private void drawCircle(Canvas canvas,float deltaRadius) { + float radius = getScaledRadius(); + canvas.drawCircle(radius + getOffsetX(), radius + getOffsetY(), + radius - deltaRadius, mPaint); + } + + // It is the callers responsibility to save and restore the canvas layers. + private void clipCanvasSoftware(Canvas canvas, Region.Op op) { + mPath.reset(); + float r = getScaledRadius(); + mPath.addCircle(r + getOffsetX(), r + getOffsetY(), r, Path.Direction.CW); + canvas.clipPath(mPath, op); + } + + // It is the callers responsibility to save and restore the canvas layers. + private void clipCanvasHardware(Canvas canvas) { + mPaint.setColor(Color.BLACK); + mPaint.setXfermode(mClipPorterDuffXfermode); + + float radius = getScaledRadius(); + mShaderMatrix.setScale(radius, radius); + mShaderMatrix.postTranslate(radius + getOffsetX(), radius + getOffsetY()); + mClipShader.setLocalMatrix(mShaderMatrix); + mPaint.setShader(mClipShader); + canvas.drawPaint(mPaint); + mPaint.setXfermode(null); + mPaint.setShader(null); } private void delegateDrawing(CellLayout delegate, int cellX, int cellY) { @@ -795,17 +840,22 @@ public class FolderIcon extends FrameLayout implements FolderListener { } if (!mBackground.drawingDelegated()) { - mBackground.drawBackground(canvas, mBgPaint); + mBackground.drawBackground(canvas); } if (mFolder == null) return; if (mFolder.getItemCount() == 0 && !mAnimating) return; - canvas.save(); + final int saveCount; - - if (mPreviewLayoutRule.clipToBackground()) { - mBackground.clipCanvas(canvas); + if (canvas.isHardwareAccelerated()) { + saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, + Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG); + } else { + saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG); + if (mPreviewLayoutRule.clipToBackground()) { + mBackground.clipCanvasSoftware(canvas, Region.Op.INTERSECT); + } } // The items are drawn in coordinates relative to the preview offset @@ -818,18 +868,24 @@ public class FolderIcon extends FrameLayout implements FolderListener { drawPreviewItem(canvas, p); } } - canvas.restore(); + canvas.translate(-mBackground.basePreviewOffsetX, -mBackground.basePreviewOffsetY); + + if (mPreviewLayoutRule.clipToBackground() && canvas.isHardwareAccelerated()) { + mBackground.clipCanvasHardware(canvas); + } + canvas.restoreToCount(saveCount); if (mPreviewLayoutRule.clipToBackground() && !mBackground.drawingDelegated()) { - mBackground.drawBackgroundStroke(canvas, mBgPaint); + mBackground.drawBackgroundStroke(canvas); } - int offsetX = mBackground.getOffsetX(); - 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) || mBadgeScale > 0) { // If we are animating to the accepting state, animate the badge out. + int offsetX = mBackground.getOffsetX(); + int offsetY = mBackground.getOffsetY(); + int previewSize = (int) (mBackground.previewSize * mBackground.mScale); + Rect bounds = new Rect(offsetX, offsetY, offsetX + previewSize, offsetY + previewSize); + float badgeScale = Math.max(0, mBadgeScale - mBackground.getScaleProgress()); mBadgeRenderer.draw(canvas, IconPalette.FOLDER_ICON_PALETTE, mBadgeInfo, bounds, badgeScale); }