From d0ae492e9faf2f5c6a686479698585d615423a34 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Thu, 11 Oct 2018 10:37:53 -0700 Subject: [PATCH] Changing the FolderIcon shape based on AdpativeIcon > Creating an abstract interface to represent a folderIcon shape > Defined few common folder shapes > Picking the folder shape closest to the AdaptiveIcon shape Bug: 111433118 Change-Id: Ib35eddbdd6692767aa9dbe6aae1a379a68cc456a --- .../launcher3/MainProcessInitializer.java | 2 + src/com/android/launcher3/Workspace.java | 6 +- src/com/android/launcher3/folder/Folder.java | 25 ++ .../folder/FolderAnimationManager.java | 13 +- .../android/launcher3/folder/FolderIcon.java | 16 +- .../android/launcher3/folder/FolderShape.java | 422 ++++++++++++++++++ .../launcher3/folder/PreviewBackground.java | 49 +- 7 files changed, 468 insertions(+), 65 deletions(-) create mode 100644 src/com/android/launcher3/folder/FolderShape.java diff --git a/src/com/android/launcher3/MainProcessInitializer.java b/src/com/android/launcher3/MainProcessInitializer.java index a18dfde310..a2538930e1 100644 --- a/src/com/android/launcher3/MainProcessInitializer.java +++ b/src/com/android/launcher3/MainProcessInitializer.java @@ -19,6 +19,7 @@ package com.android.launcher3; import android.content.Context; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.folder.FolderShape; import com.android.launcher3.graphics.IconShapeOverride; import com.android.launcher3.logging.FileLog; import com.android.launcher3.util.ResourceBasedOverride; @@ -39,5 +40,6 @@ public class MainProcessInitializer implements ResourceBasedOverride { FeatureFlags.initialize(context); IconShapeOverride.apply(context); SessionCommitReceiver.applyDefaultUserPrefs(context); + FolderShape.init(); } } diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 11e601c7f3..3f7d68d12f 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -127,8 +127,8 @@ public class Workspace extends PagedView private static final int DEFAULT_PAGE = 0; - private static final boolean MAP_NO_RECURSE = false; - private static final boolean MAP_RECURSE = true; + public static final boolean MAP_NO_RECURSE = false; + public static final boolean MAP_RECURSE = true; // The screen id used for the empty screen always present to the right. public static final int EXTRA_EMPTY_SCREEN_ID = -201; @@ -3121,7 +3121,7 @@ public class Workspace extends PagedView * @param recurse true: iterate over folder children. false: op get the folders themselves. * @param op the operator to map over the shortcuts */ - void mapOverItems(boolean recurse, ItemOperator op) { + public void mapOverItems(boolean recurse, ItemOperator op) { ArrayList containers = getAllShortcutAndWidgetContainers(); final int containerCount = containers.size(); for (int containerIdx = 0; containerIdx < containerCount; containerIdx++) { diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index c4d1058005..94c8d4549d 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -26,6 +26,8 @@ import android.animation.AnimatorSet; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.Path; import android.graphics.Rect; import android.text.InputType; import android.text.Selection; @@ -151,6 +153,8 @@ public class Folder extends AbstractFloatingView implements DragSource, // Cell ranks used for drag and drop @Thunk int mTargetRank, mPrevTargetRank, mEmptyCellRank; + private Path mClipPath; + @ViewDebug.ExportedProperty(category = "launcher", mapping = { @ViewDebug.IntToString(from = STATE_NONE, to = "STATE_NONE"), @@ -1476,4 +1480,25 @@ public class Folder extends AbstractFloatingView implements DragSource, sHintText = res.getString(R.string.folder_hint_text); } } + + /** + * Alternative to using {@link #getClipToOutline()} as it only works with derivatives of + * rounded rect. + */ + public void setClipPath(Path clipPath) { + mClipPath = clipPath; + invalidate(); + } + + @Override + public void draw(Canvas canvas) { + if (mClipPath != null) { + int count = canvas.save(); + canvas.clipPath(mClipPath); + super.draw(canvas); + canvas.restoreToCount(count); + } else { + super.draw(canvas); + } + } } diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java index 1277a20909..fa890b99df 100644 --- a/src/com/android/launcher3/folder/FolderAnimationManager.java +++ b/src/com/android/launcher3/folder/FolderAnimationManager.java @@ -19,6 +19,7 @@ package com.android.launcher3.folder; import static com.android.launcher3.BubbleTextView.TEXT_ALPHA_PROPERTY; import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW; +import static com.android.launcher3.folder.FolderShape.getShape; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -40,7 +41,6 @@ import com.android.launcher3.R; import com.android.launcher3.ShortcutAndWidgetContainer; import com.android.launcher3.Utilities; import com.android.launcher3.anim.PropertyResetListener; -import com.android.launcher3.anim.RoundedRectRevealOutlineProvider; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.util.Themes; @@ -166,7 +166,6 @@ public class FolderAnimationManager { Math.round((totalOffsetX + initialSize) / initialScale), Math.round((paddingOffsetY + initialSize) / initialScale)); Rect endRect = new Rect(0, 0, lp.width, lp.height); - float initialRadius = initialSize / initialScale / 2f; float finalRadius = Utilities.pxFromDp(2, mContext.getResources().getDisplayMetrics()); // Create the animators. @@ -189,14 +188,8 @@ public class FolderAnimationManager { play(a, getAnimator(mFolder, SCALE_PROPERTY, initialScale, finalScale)); play(a, getAnimator(mFolderBackground, "color", initialColor, finalColor)); play(a, mFolderIcon.mFolderName.createTextAlphaAnimator(!mIsOpening)); - RoundedRectRevealOutlineProvider outlineProvider = new RoundedRectRevealOutlineProvider( - initialRadius, finalRadius, startRect, endRect) { - @Override - public boolean shouldRemoveElevationDuringAnimation() { - return true; - } - }; - play(a, outlineProvider.createRevealAnimator(mFolder, !mIsOpening)); + play(a, getShape().createRevealAnimator( + mFolder, startRect, endRect, finalRadius, !mIsOpening)); // Animate the elevation midway so that the shadow is not noticeable in the background. int midDuration = mDuration / 2; diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index d09f03612f..429d44fcb6 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -75,6 +75,7 @@ import androidx.annotation.NonNull; * An icon that can appear on in the workspace representing an {@link Folder}. */ public class FolderIcon extends FrameLayout implements FolderListener { + @Thunk Launcher mLauncher; @Thunk Folder mFolder; private FolderInfo mInfo; @@ -477,20 +478,9 @@ public class FolderIcon extends FrameLayout implements FolderListener { if (mFolder == null) return; if (mFolder.getItemCount() == 0 && !mAnimating) return; - final int saveCount; - - if (canvas.isHardwareAccelerated()) { - saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null); - } else { - saveCount = canvas.save(); - canvas.clipPath(mBackground.getClipPath()); - } - + final int saveCount = canvas.save(); + canvas.clipPath(mBackground.getClipPath()); mPreviewItemManager.draw(canvas); - - if (canvas.isHardwareAccelerated()) { - mBackground.clipCanvasHardware(canvas); - } canvas.restoreToCount(saveCount); if (!mBackground.drawingDelegated()) { diff --git a/src/com/android/launcher3/folder/FolderShape.java b/src/com/android/launcher3/folder/FolderShape.java new file mode 100644 index 0000000000..ae279cb3dc --- /dev/null +++ b/src/com/android/launcher3/folder/FolderShape.java @@ -0,0 +1,422 @@ +/* + * Copyright (C) 2018 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.folder; + +import static com.android.launcher3.Workspace.MAP_NO_RECURSE; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.FloatArrayEvaluator; +import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.annotation.TargetApi; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Rect; +import android.graphics.Region; +import android.graphics.Region.Op; +import android.graphics.RegionIterator; +import android.graphics.drawable.AdaptiveIconDrawable; +import android.graphics.drawable.ColorDrawable; +import android.os.Build; +import android.view.ViewOutlineProvider; + +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.MainThreadExecutor; +import com.android.launcher3.Utilities; +import com.android.launcher3.anim.RoundedRectRevealOutlineProvider; + +/** + * Abstract representation of the shape of a folder icon + */ +public abstract class FolderShape { + + private static FolderShape sInstance = new Circle(); + + public static FolderShape getShape() { + return sInstance; + } + + private static FolderShape[] getAllShapes() { + return new FolderShape[] { + new Circle(), + new RoundedSquare(8f / 50), // Ratios based on path defined in config_icon_mask + new RoundedSquare(30f / 50), + new Square(), + new TearDrop(), + new Squircle()}; + } + + public abstract void drawShape(Canvas canvas, float offsetX, float offsetY, float radius, + Paint paint); + + public abstract void addShape(Path path, float offsetX, float offsetY, float radius); + + public abstract Animator createRevealAnimator(Folder target, Rect startRect, Rect endRect, + float endRadius, boolean isReversed); + + /** + * Abstract shape where the reveal animation is a derivative of a round rect animation + */ + private static abstract class SimpleRectShape extends FolderShape { + + @Override + public final Animator createRevealAnimator(Folder target, Rect startRect, Rect endRect, + float endRadius, boolean isReversed) { + return new RoundedRectRevealOutlineProvider( + getStartRadius(startRect), endRadius, startRect, endRect) { + @Override + public boolean shouldRemoveElevationDuringAnimation() { + return true; + } + }.createRevealAnimator(target, isReversed); + } + + protected abstract float getStartRadius(Rect startRect); + } + + /** + * Abstract shape which draws using {@link Path} + */ + private static abstract class PathShape extends FolderShape { + + private final Path mTmpPath = new Path(); + + @Override + public final void drawShape(Canvas canvas, float offsetX, float offsetY, float radius, + Paint paint) { + mTmpPath.reset(); + addShape(mTmpPath, offsetX, offsetY, radius); + canvas.drawPath(mTmpPath, paint); + } + + protected abstract AnimatorUpdateListener newUpdateListener( + Rect startRect, Rect endRect, float endRadius, Path outPath); + + @Override + public final Animator createRevealAnimator(Folder target, Rect startRect, Rect endRect, + float endRadius, boolean isReversed) { + Path path = new Path(); + AnimatorUpdateListener listener = + newUpdateListener(startRect, endRect, endRadius, path); + + ValueAnimator va = + isReversed ? ValueAnimator.ofFloat(1f, 0f) : ValueAnimator.ofFloat(0f, 1f); + va.addListener(new AnimatorListenerAdapter() { + private ViewOutlineProvider mOldOutlineProvider; + + public void onAnimationStart(Animator animation) { + mOldOutlineProvider = target.getOutlineProvider(); + target.setOutlineProvider(null); + + target.setTranslationZ(-target.getElevation()); + } + + public void onAnimationEnd(Animator animation) { + target.setTranslationZ(0); + target.setClipPath(null); + target.setOutlineProvider(mOldOutlineProvider); + } + }); + + va.addUpdateListener((anim) -> { + path.reset(); + listener.onAnimationUpdate(anim); + target.setClipPath(path); + }); + + return va; + } + } + + public static final class Circle extends SimpleRectShape { + + @Override + public void drawShape(Canvas canvas, float offsetX, float offsetY, float radius, Paint p) { + canvas.drawCircle(radius + offsetX, radius + offsetY, radius, p); + } + + @Override + public void addShape(Path path, float offsetX, float offsetY, float radius) { + path.addCircle(radius + offsetX, radius + offsetY, radius, Path.Direction.CW); + } + + @Override + protected float getStartRadius(Rect startRect) { + return startRect.width() / 2f; + } + } + + public static class Square extends SimpleRectShape { + + @Override + public void drawShape(Canvas canvas, float offsetX, float offsetY, float radius, Paint p) { + float cx = radius + offsetX; + float cy = radius + offsetY; + canvas.drawRect(cx - radius, cy - radius, cx + radius, cy + radius, p); + } + + @Override + public void addShape(Path path, float offsetX, float offsetY, float radius) { + float cx = radius + offsetX; + float cy = radius + offsetY; + path.addRect(cx - radius, cy - radius, cx + radius, cy + radius, Path.Direction.CW); + } + + @Override + protected float getStartRadius(Rect startRect) { + return 0; + } + } + + public static class RoundedSquare extends SimpleRectShape { + + /** + * Ratio of corner radius to half size. Based on the + */ + private final float mRadiusFactor; + + public RoundedSquare(float radiusFactor) { + mRadiusFactor = radiusFactor; + } + + @Override + public void drawShape(Canvas canvas, float offsetX, float offsetY, float radius, Paint p) { + float cx = radius + offsetX; + float cy = radius + offsetY; + float cr = radius * mRadiusFactor; + canvas.drawRoundRect(cx - radius, cy - radius, cx + radius, cy + radius, cr, cr, p); + } + + @Override + public void addShape(Path path, float offsetX, float offsetY, float radius) { + float cx = radius + offsetX; + float cy = radius + offsetY; + float cr = radius * mRadiusFactor; + path.addRoundRect(cx - radius, cy - radius, cx + radius, cy + radius, cr, cr, + Path.Direction.CW); + } + + @Override + protected float getStartRadius(Rect startRect) { + return (startRect.width() / 2f) * mRadiusFactor; + } + } + + public static class TearDrop extends PathShape { + + /** + * Radio of short radius to large radius, based on the shape options defined in the config. + */ + private static final float RADIUS_RATIO = 15f / 50; + + private final float[] mTempRadii = new float[8]; + + @Override + public void addShape(Path p, float offsetX, float offsetY, float r1) { + float r2 = r1 * RADIUS_RATIO; + float cx = r1 + offsetX; + float cy = r1 + offsetY; + + p.addRoundRect(cx - r1, cy - r1, cx + r1, cy + r1, getRadiiArray(r1, r2), + Path.Direction.CW); + } + + private float[] getRadiiArray(float r1, float r2) { + mTempRadii[0] = mTempRadii [1] = mTempRadii[2] = mTempRadii[3] = + mTempRadii[6] = mTempRadii[7] = r1; + mTempRadii[4] = mTempRadii[5] = r2; + return mTempRadii; + } + + @Override + protected AnimatorUpdateListener newUpdateListener(Rect startRect, Rect endRect, + float endRadius, Path outPath) { + float r1 = startRect.width() / 2f; + float r2 = r1 * RADIUS_RATIO; + + float[] startValues = new float[] { + startRect.left, startRect.top, startRect.right, startRect.bottom, r1, r2}; + float[] endValues = new float[] { + endRect.left, endRect.top, endRect.right, endRect.bottom, endRadius, endRadius}; + + FloatArrayEvaluator evaluator = new FloatArrayEvaluator(new float[6]); + + return (anim) -> { + float progress = (Float) anim.getAnimatedValue(); + float[] values = evaluator.evaluate(progress, startValues, endValues); + outPath.addRoundRect( + values[0], values[1], values[2], values[3], + getRadiiArray(values[4], values[5]), Path.Direction.CW); + }; + } + } + + public static class Squircle extends PathShape { + + /** + * Radio of radius to circle radius, based on the shape options defined in the config. + */ + private static final float RADIUS_RATIO = 10f / 50; + + @Override + public void addShape(Path p, float offsetX, float offsetY, float r) { + float cx = r + offsetX; + float cy = r + offsetY; + float control = r - r * RADIUS_RATIO; + + p.moveTo(cx, cy - r); + addLeftCurve(cx, cy, r, control, p); + addRightCurve(cx, cy, r, control, p); + addLeftCurve(cx, cy, -r, -control, p); + addRightCurve(cx, cy, -r, -control, p); + p.close(); + } + + private void addLeftCurve(float cx, float cy, float r, float control, Path path) { + path.cubicTo( + cx - control, cy - r, + cx - r, cy - control, + cx - r, cy); + } + + private void addRightCurve(float cx, float cy, float r, float control, Path path) { + path.cubicTo( + cx - r, cy + control, + cx - control, cy + r, + cx, cy + r); + } + + @Override + protected AnimatorUpdateListener newUpdateListener(Rect startRect, Rect endRect, + float endR, Path outPath) { + + float startCX = startRect.exactCenterX(); + float startCY = startRect.exactCenterY(); + float startR = startRect.width() / 2f; + float startControl = startR - startR * RADIUS_RATIO; + float startHShift = 0; + float startVShift = 0; + + float endCX = endRect.exactCenterX(); + float endCY = endRect.exactCenterY(); + // Approximate corner circle using bezier curves + // http://spencermortensen.com/articles/bezier-circle/ + float endControl = endR * 0.551915024494f; + float endHShift = endRect.width() / 2f - endR; + float endVShift = endRect.height() / 2f - endR; + + return (anim) -> { + float progress = (Float) anim.getAnimatedValue(); + + float cx = (1 - progress) * startCX + progress * endCX; + float cy = (1 - progress) * startCY + progress * endCY; + float r = (1 - progress) * startR + progress * endR; + float control = (1 - progress) * startControl + progress * endControl; + float hShift = (1 - progress) * startHShift + progress * endHShift; + float vShift = (1 - progress) * startVShift + progress * endVShift; + + outPath.moveTo(cx, cy - vShift - r); + outPath.rLineTo(-hShift, 0); + + addLeftCurve(cx - hShift, cy - vShift, r, control, outPath); + outPath.rLineTo(0, vShift + vShift); + + addRightCurve(cx - hShift, cy + vShift, r, control, outPath); + outPath.rLineTo(hShift + hShift, 0); + + addLeftCurve(cx + hShift, cy + vShift, -r, -control, outPath); + outPath.rLineTo(0, -vShift - vShift); + + addRightCurve(cx + hShift, cy - vShift, -r, -control, outPath); + outPath.close(); + }; + } + } + + /** + * Initializes the shape which is closest to closest to the {@link AdaptiveIconDrawable} + */ + public static void init() { + if (!Utilities.ATLEAST_OREO) { + return; + } + new MainThreadExecutor().execute(FolderShape::pickShapeInBackground); + } + + @TargetApi(Build.VERSION_CODES.O) + protected static void pickShapeInBackground() { + // Pick any large size + int size = 200; + + Region full = new Region(0, 0, size, size); + Region iconR = new Region(); + AdaptiveIconDrawable drawable = new AdaptiveIconDrawable( + new ColorDrawable(Color.BLACK), new ColorDrawable(Color.BLACK)); + drawable.setBounds(0, 0, size, size); + iconR.setPath(drawable.getIconMask(), full); + + Path shapePath = new Path(); + Region shapeR = new Region(); + Rect tempRect = new Rect(); + + // Find the shape with minimum area of divergent region. + int minArea = Integer.MAX_VALUE; + FolderShape closestShape = null; + for (FolderShape shape : getAllShapes()) { + shapePath.reset(); + shape.addShape(shapePath, 0, 0, size / 2f); + shapeR.setPath(shapePath, full); + shapeR.op(iconR, Op.XOR); + + RegionIterator itr = new RegionIterator(shapeR); + int area = 0; + + while (itr.next(tempRect)) { + area += tempRect.width() * tempRect.height(); + } + if (area < minArea) { + minArea = area; + closestShape = shape; + } + } + + if (closestShape != null) { + FolderShape shape = closestShape; + new MainThreadExecutor().execute(() -> updateFolderShape(shape)); + } + } + + private static void updateFolderShape(FolderShape shape) { + sInstance = shape; + LauncherAppState app = LauncherAppState.getInstanceNoCreate(); + if (app == null) { + return; + } + Launcher launcher = (Launcher) app.getModel().getCallback(); + if (launcher != null) { + launcher.getWorkspace().mapOverItems(MAP_NO_RECURSE, (i, v) -> { + if (v instanceof FolderIcon) { + v.invalidate(); + } + return false; + }); + } + } +} diff --git a/src/com/android/launcher3/folder/PreviewBackground.java b/src/com/android/launcher3/folder/PreviewBackground.java index ceb1a8c372..8443953bd9 100644 --- a/src/com/android/launcher3/folder/PreviewBackground.java +++ b/src/com/android/launcher3/folder/PreviewBackground.java @@ -16,6 +16,8 @@ package com.android.launcher3.folder; +import static com.android.launcher3.folder.FolderShape.getShape; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; @@ -49,16 +51,6 @@ public class PreviewBackground { private static final int CONSUMPTION_ANIMATION_DURATION = 100; - 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; @@ -208,8 +200,7 @@ public class PreviewBackground { mPaint.setStyle(Paint.Style.FILL); mPaint.setColor(getBgColor()); - drawCircle(canvas, 0 /* deltaRadius */); - + getShape().drawShape(canvas, getOffsetX(), getOffsetY(), getScaledRadius(), mPaint); drawShadow(canvas); } @@ -244,7 +235,7 @@ public class PreviewBackground { mPaint.setShader(null); if (canvas.isHardwareAccelerated()) { mPaint.setXfermode(mShadowPorterDuffXfermode); - canvas.drawCircle(radius + offsetX, radius + offsetY, radius, mPaint); + getShape().drawShape(canvas, offsetX, offsetY, radius, mPaint); mPaint.setXfermode(null); } @@ -287,7 +278,10 @@ public class PreviewBackground { mPaint.setColor(ColorUtils.setAlphaComponent(mBgColor, mStrokeAlpha)); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(mStrokeWidth); - drawCircle(canvas, 1 /* deltaRadius */); + + float inset = 1f; + getShape().drawShape(canvas, + getOffsetX() + inset, getOffsetY() + inset, getScaledRadius() - inset, mPaint); } public void drawLeaveBehind(Canvas canvas) { @@ -296,40 +290,17 @@ public class PreviewBackground { mPaint.setStyle(Paint.Style.FILL); mPaint.setColor(Color.argb(160, 245, 245, 245)); - drawCircle(canvas, 0 /* deltaRadius */); + getShape().drawShape(canvas, getOffsetX(), getOffsetY(), getScaledRadius(), mPaint); mScale = originalScale; } - private void drawCircle(Canvas canvas,float deltaRadius) { - float radius = getScaledRadius(); - canvas.drawCircle(radius + getOffsetX(), radius + getOffsetY(), - radius - deltaRadius, mPaint); - } - public Path getClipPath() { mPath.reset(); - float r = getScaledRadius(); - mPath.addCircle(r + getOffsetX(), r + getOffsetY(), r, Path.Direction.CW); + getShape().addShape(mPath, getOffsetX(), getOffsetY(), getScaledRadius()); return mPath; } - // It is the callers responsibility to save and restore the canvas layers. - void clipCanvasHardware(Canvas canvas) { - mPaint.setColor(Color.BLACK); - mPaint.setStyle(Paint.Style.FILL); - 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) { if (mDrawingDelegate != delegate) { delegate.addFolderBackground(this);