diff options
-rw-r--r-- | src/com/android/launcher3/CellLayout.java | 13 | ||||
-rw-r--r-- | src/com/android/launcher3/Workspace.java | 7 | ||||
-rw-r--r-- | src/com/android/launcher3/folder/FolderAnimationManager.java | 2 | ||||
-rw-r--r-- | src/com/android/launcher3/folder/FolderIcon.java | 401 | ||||
-rw-r--r-- | src/com/android/launcher3/folder/PreviewBackground.java | 430 |
5 files changed, 442 insertions, 411 deletions
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index d0d33a03d..d07e3303d 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -53,6 +53,7 @@ import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper; import com.android.launcher3.anim.PropertyListBuilder; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.folder.FolderIcon; +import com.android.launcher3.folder.PreviewBackground; import com.android.launcher3.graphics.DragPreviewProvider; import com.android.launcher3.util.CellAndSpan; import com.android.launcher3.util.GridOccupancy; @@ -102,8 +103,8 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { private OnTouchListener mInterceptTouchListener; private final StylusEventHelper mStylusEventHelper; - private final ArrayList<FolderIcon.PreviewBackground> mFolderBackgrounds = new ArrayList<>(); - final FolderIcon.PreviewBackground mFolderLeaveBehind = new FolderIcon.PreviewBackground(); + private final ArrayList<PreviewBackground> mFolderBackgrounds = new ArrayList<>(); + final PreviewBackground mFolderLeaveBehind = new PreviewBackground(); private float mBackgroundAlpha; @@ -495,7 +496,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { } for (int i = 0; i < mFolderBackgrounds.size(); i++) { - FolderIcon.PreviewBackground bg = mFolderBackgrounds.get(i); + PreviewBackground bg = mFolderBackgrounds.get(i); cellToPoint(bg.delegateCellX, bg.delegateCellY, mTempLocation); canvas.save(); canvas.translate(mTempLocation[0], mTempLocation[1]); @@ -521,7 +522,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { super.dispatchDraw(canvas); for (int i = 0; i < mFolderBackgrounds.size(); i++) { - FolderIcon.PreviewBackground bg = mFolderBackgrounds.get(i); + PreviewBackground bg = mFolderBackgrounds.get(i); if (bg.isClipping) { cellToPoint(bg.delegateCellX, bg.delegateCellY, mTempLocation); canvas.save(); @@ -532,10 +533,10 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { } } - public void addFolderBackground(FolderIcon.PreviewBackground bg) { + public void addFolderBackground(PreviewBackground bg) { mFolderBackgrounds.add(bg); } - public void removeFolderBackground(FolderIcon.PreviewBackground bg) { + public void removeFolderBackground(PreviewBackground bg) { mFolderBackgrounds.remove(bg); } diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 517073aca..0fabeebbf 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -71,6 +71,7 @@ import com.android.launcher3.dragndrop.DragView; import com.android.launcher3.dragndrop.SpringLoadedDragController; import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderIcon; +import com.android.launcher3.folder.PreviewBackground; import com.android.launcher3.graphics.DragPreviewProvider; import com.android.launcher3.graphics.PreloadIconDrawable; import com.android.launcher3.popup.PopupContainerWithArrow; @@ -254,7 +255,7 @@ public class Workspace extends PagedView public static final int REORDER_TIMEOUT = 350; private final Alarm mFolderCreationAlarm = new Alarm(); private final Alarm mReorderAlarm = new Alarm(); - private FolderIcon.PreviewBackground mFolderCreateBg; + private PreviewBackground mFolderCreateBg; private FolderIcon mDragOverFolderIcon = null; private boolean mCreateUserFolderOnDrop = false; private boolean mAddToExistingFolderOnDrop = false; @@ -2374,7 +2375,7 @@ public class Workspace extends PagedView // In order to keep everything continuous, we hand off the currently rendered // folder background to the newly created icon. This preserves animation state. fi.setFolderBackground(mFolderCreateBg); - mFolderCreateBg = new FolderIcon.PreviewBackground(); + mFolderCreateBg = new PreviewBackground(); fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale, postAnimationRunnable); } else { @@ -3055,7 +3056,7 @@ public class Workspace extends PagedView final int cellX; final int cellY; - final FolderIcon.PreviewBackground bg = new FolderIcon.PreviewBackground(); + final PreviewBackground bg = new PreviewBackground(); public FolderCreationAlarmListener(CellLayout layout, int cellX, int cellY) { this.layout = layout; diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java index 74e8d3b29..cb86b59f3 100644 --- a/src/com/android/launcher3/folder/FolderAnimationManager.java +++ b/src/com/android/launcher3/folder/FolderAnimationManager.java @@ -57,7 +57,7 @@ public class FolderAnimationManager { private GradientDrawable mFolderBackground; private FolderIcon mFolderIcon; - private FolderIcon.PreviewBackground mPreviewBackground; + private PreviewBackground mPreviewBackground; private Context mContext; private Launcher mLauncher; diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index fa148c889..215a31c8f 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -23,21 +23,12 @@ import android.animation.ValueAnimator; 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.Point; -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.support.annotation.NonNull; -import android.support.v4.graphics.ColorUtils; import android.util.AttributeSet; import android.util.Property; import android.view.LayoutInflater; @@ -57,7 +48,6 @@ import com.android.launcher3.CellLayout; import com.android.launcher3.CheckLongPressHelper; import com.android.launcher3.DeviceProfile; import com.android.launcher3.DropTarget.DragObject; -import com.android.launcher3.FastBitmapDrawable; import com.android.launcher3.FolderInfo; import com.android.launcher3.FolderInfo.FolderListener; import com.android.launcher3.ItemInfo; @@ -76,8 +66,6 @@ import com.android.launcher3.badge.FolderBadgeInfo; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.dragndrop.DragView; -import com.android.launcher3.graphics.PreloadIconDrawable; -import com.android.launcher3.util.Themes; import com.android.launcher3.graphics.IconPalette; import com.android.launcher3.util.Thunk; import com.android.launcher3.widget.PendingAddShortcutInfo; @@ -101,8 +89,6 @@ public class FolderIcon extends FrameLayout implements FolderListener { private CheckLongPressHelper mLongPressHelper; private StylusEventHelper mStylusEventHelper; - // The number of icons to display in the - private static final int CONSUMPTION_ANIMATION_DURATION = 100; private static final int DROP_IN_ANIMATION_DURATION = 400; private static final int INITIAL_ITEM_ANIMATION_DURATION = 350; private static final int FINAL_ITEM_ANIMATION_DURATION = 200; @@ -521,393 +507,6 @@ public class FolderIcon extends FrameLayout implements FolderListener { canvas.restore(); } - /** - * This object represents a FolderIcon preview background. It stores drawing / measurement - * 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 int mBgColor; - private float mStrokeWidth; - private int mStrokeAlpha = MAX_BG_OPACITY; - private int mShadowAlpha = 255; - private View mInvalidateDelegate; - - public int previewSize; - private int basePreviewOffsetX; - private int basePreviewOffsetY; - - private CellLayout mDrawingDelegate; - public int delegateCellX; - public int delegateCellY; - - // When the PreviewBackground is drawn under an icon (for creating a folder) the border - // should not occlude the icon - public boolean isClipping = true; - - // Drawing / animation configurations - private static final float ACCEPT_SCALE_FACTOR = 1.25f; - private static final float ACCEPT_COLOR_MULTIPLIER = 1.5f; - - // Expressed on a scale from 0 to 255. - private static final int BG_OPACITY = 160; - private static final int MAX_BG_OPACITY = 225; - private static final int SHADOW_OPACITY = 40; - - ValueAnimator mScaleAnimator; - ObjectAnimator mStrokeAlphaAnimator; - ObjectAnimator mShadowAnimator; - - private static final Property<PreviewBackground, Integer> STROKE_ALPHA = - new Property<PreviewBackground, Integer>(Integer.class, "strokeAlpha") { - @Override - public Integer get(PreviewBackground previewBackground) { - return previewBackground.mStrokeAlpha; - } - - @Override - public void set(PreviewBackground previewBackground, Integer alpha) { - previewBackground.mStrokeAlpha = alpha; - previewBackground.invalidate(); - } - }; - - private static final Property<PreviewBackground, Integer> SHADOW_ALPHA = - new Property<PreviewBackground, Integer>(Integer.class, "shadowAlpha") { - @Override - public Integer get(PreviewBackground previewBackground) { - return previewBackground.mShadowAlpha; - } - - @Override - public void set(PreviewBackground previewBackground, Integer alpha) { - previewBackground.mShadowAlpha = alpha; - previewBackground.invalidate(); - } - }; - - public void setup(Launcher launcher, View invalidateDelegate, - int availableSpace, int topPadding) { - mInvalidateDelegate = invalidateDelegate; - mBgColor = Themes.getAttrColor(launcher, android.R.attr.colorPrimary); - - DeviceProfile grid = launcher.getDeviceProfile(); - final int previewSize = grid.folderIconSizePx; - final int previewPadding = grid.folderIconPreviewPadding; - - this.previewSize = (previewSize - 2 * previewPadding); - - basePreviewOffsetX = (availableSpace - this.previewSize) / 2; - basePreviewOffsetY = previewPadding + grid.folderBackgroundOffset + topPadding; - - // Stroke width is 1dp - mStrokeWidth = launcher.getResources().getDisplayMetrics().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(); - } - - int getRadius() { - return previewSize / 2; - } - - int getScaledRadius() { - return (int) (mScale * getRadius()); - } - - int getOffsetX() { - return basePreviewOffsetX - (getScaledRadius() - getRadius()); - } - - int getOffsetY() { - 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() { - if (mInvalidateDelegate != null) { - mInvalidateDelegate.invalidate(); - } - - if (mDrawingDelegate != null) { - mDrawingDelegate.invalidate(); - } - } - - void setInvalidateDelegate(View invalidateDelegate) { - mInvalidateDelegate = invalidateDelegate; - invalidate(); - } - - public void drawBackground(Canvas canvas) { - mPaint.setStyle(Paint.Style.FILL); - int alpha = (int) Math.min(MAX_BG_OPACITY, BG_OPACITY * mColorMultiplier); - mPaint.setColor(ColorUtils.setAlphaComponent(mBgColor, alpha)); - - 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); - - } else { - saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG); - clipCanvasSoftware(canvas, Region.Op.DIFFERENCE); - } - - mShaderMatrix.setScale(shadowRadius, shadowRadius); - mShaderMatrix.postTranslate(radius + offsetX, shadowRadius + offsetY); - mShadowShader.setLocalMatrix(mShaderMatrix); - mPaint.setAlpha(mShadowAlpha); - mPaint.setShader(mShadowShader); - canvas.drawPaint(mPaint); - mPaint.setAlpha(255); - mPaint.setShader(null); - if (canvas.isHardwareAccelerated()) { - mPaint.setXfermode(mShadowPorterDuffXfermode); - canvas.drawCircle(radius + offsetX, radius + offsetY, radius, mPaint); - mPaint.setXfermode(null); - } - - canvas.restoreToCount(saveCount); - } - - public void fadeInBackgroundShadow() { - if (mShadowAnimator != null) { - mShadowAnimator.cancel(); - } - mShadowAnimator = ObjectAnimator - .ofInt(this, SHADOW_ALPHA, 0, 255) - .setDuration(100); - mShadowAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mShadowAnimator = null; - } - }); - mShadowAnimator.start(); - } - - public void animateBackgroundStroke() { - if (mStrokeAlphaAnimator != null) { - mStrokeAlphaAnimator.cancel(); - } - mStrokeAlphaAnimator = ObjectAnimator - .ofInt(this, STROKE_ALPHA, MAX_BG_OPACITY / 2, MAX_BG_OPACITY) - .setDuration(100); - mStrokeAlphaAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mStrokeAlphaAnimator = null; - } - }); - mStrokeAlphaAnimator.start(); - } - - public void drawBackgroundStroke(Canvas canvas) { - mPaint.setColor(ColorUtils.setAlphaComponent(mBgColor, mStrokeAlpha)); - mPaint.setStyle(Paint.Style.STROKE); - mPaint.setStrokeWidth(mStrokeWidth); - drawCircle(canvas, 1 /* deltaRadius */); - } - - public void drawLeaveBehind(Canvas canvas) { - float originalScale = mScale; - mScale = 0.5f; - - mPaint.setStyle(Paint.Style.FILL); - mPaint.setColor(Color.argb(160, 245, 245, 245)); - drawCircle(canvas, 0 /* deltaRadius */); - - mScale = originalScale; - } - - 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) { - if (mDrawingDelegate != delegate) { - delegate.addFolderBackground(this); - } - - mDrawingDelegate = delegate; - delegateCellX = cellX; - delegateCellY = cellY; - - invalidate(); - } - - private void clearDrawingDelegate() { - if (mDrawingDelegate != null) { - mDrawingDelegate.removeFolderBackground(this); - } - - mDrawingDelegate = null; - invalidate(); - } - - private boolean drawingDelegated() { - return mDrawingDelegate != null; - } - - private void animateScale(float finalScale, float finalMultiplier, - final Runnable onStart, final Runnable onEnd) { - final float scale0 = mScale; - final float scale1 = finalScale; - - final float bgMultiplier0 = mColorMultiplier; - final float bgMultiplier1 = finalMultiplier; - - if (mScaleAnimator != null) { - mScaleAnimator.cancel(); - } - - mScaleAnimator = LauncherAnimUtils.ofFloat(0f, 1.0f); - - mScaleAnimator.addUpdateListener(new AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float prog = animation.getAnimatedFraction(); - mScale = prog * scale1 + (1 - prog) * scale0; - mColorMultiplier = prog * bgMultiplier1 + (1 - prog) * bgMultiplier0; - invalidate(); - } - }); - mScaleAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - if (onStart != null) { - onStart.run(); - } - } - - @Override - public void onAnimationEnd(Animator animation) { - if (onEnd != null) { - onEnd.run(); - } - mScaleAnimator = null; - } - }); - - mScaleAnimator.setDuration(CONSUMPTION_ANIMATION_DURATION); - mScaleAnimator.start(); - } - - public void animateToAccept(final CellLayout cl, final int cellX, final int cellY) { - Runnable onStart = new Runnable() { - @Override - public void run() { - delegateDrawing(cl, cellX, cellY); - } - }; - animateScale(ACCEPT_SCALE_FACTOR, ACCEPT_COLOR_MULTIPLIER, onStart, null); - } - - public void animateToRest() { - // This can be called multiple times -- we need to make sure the drawing delegate - // is saved and restored at the beginning of the animation, since cancelling the - // existing animation can clear the delgate. - final CellLayout cl = mDrawingDelegate; - final int cellX = delegateCellX; - final int cellY = delegateCellY; - - Runnable onStart = new Runnable() { - @Override - public void run() { - delegateDrawing(cl, cellX, cellY); - } - }; - Runnable onEnd = new Runnable() { - @Override - public void run() { - clearDrawingDelegate(); - } - }; - animateScale(1f, 1f, onStart, onEnd); - } - - public int getBackgroundAlpha() { - return (int) Math.min(MAX_BG_OPACITY, BG_OPACITY * mColorMultiplier); - } - - public float getStrokeWidth() { - return mStrokeWidth; - } - } - public void setFolderBackground(PreviewBackground bg) { mBackground = bg; mBackground.setInvalidateDelegate(this); diff --git a/src/com/android/launcher3/folder/PreviewBackground.java b/src/com/android/launcher3/folder/PreviewBackground.java new file mode 100644 index 000000000..44ebbcda7 --- /dev/null +++ b/src/com/android/launcher3/folder/PreviewBackground.java @@ -0,0 +1,430 @@ +/* + * Copyright (C) 2017 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 android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +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.Region; +import android.graphics.Shader; +import android.support.v4.graphics.ColorUtils; +import android.util.Property; +import android.view.View; + +import com.android.launcher3.CellLayout; +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAnimUtils; +import com.android.launcher3.util.Themes; + +/** + * This object represents a FolderIcon preview background. It stores drawing / measurement + * information, handles drawing, and animation (accept state <--> rest state). + */ +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; + + private final Matrix mShaderMatrix = new Matrix(); + private final Path mPath = new Path(); + + private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + + float mScale = 1f; + private float mColorMultiplier = 1f; + private int mBgColor; + private float mStrokeWidth; + private int mStrokeAlpha = MAX_BG_OPACITY; + private int mShadowAlpha = 255; + private View mInvalidateDelegate; + + int previewSize; + int basePreviewOffsetX; + int basePreviewOffsetY; + + private CellLayout mDrawingDelegate; + public int delegateCellX; + public int delegateCellY; + + // When the PreviewBackground is drawn under an icon (for creating a folder) the border + // should not occlude the icon + public boolean isClipping = true; + + // Drawing / animation configurations + private static final float ACCEPT_SCALE_FACTOR = 1.25f; + private static final float ACCEPT_COLOR_MULTIPLIER = 1.5f; + + // Expressed on a scale from 0 to 255. + private static final int BG_OPACITY = 160; + private static final int MAX_BG_OPACITY = 225; + private static final int SHADOW_OPACITY = 40; + + private ValueAnimator mScaleAnimator; + private ObjectAnimator mStrokeAlphaAnimator; + private ObjectAnimator mShadowAnimator; + + private static final Property<PreviewBackground, Integer> STROKE_ALPHA = + new Property<PreviewBackground, Integer>(Integer.class, "strokeAlpha") { + @Override + public Integer get(PreviewBackground previewBackground) { + return previewBackground.mStrokeAlpha; + } + + @Override + public void set(PreviewBackground previewBackground, Integer alpha) { + previewBackground.mStrokeAlpha = alpha; + previewBackground.invalidate(); + } + }; + + private static final Property<PreviewBackground, Integer> SHADOW_ALPHA = + new Property<PreviewBackground, Integer>(Integer.class, "shadowAlpha") { + @Override + public Integer get(PreviewBackground previewBackground) { + return previewBackground.mShadowAlpha; + } + + @Override + public void set(PreviewBackground previewBackground, Integer alpha) { + previewBackground.mShadowAlpha = alpha; + previewBackground.invalidate(); + } + }; + + public void setup(Launcher launcher, View invalidateDelegate, + int availableSpace, int topPadding) { + mInvalidateDelegate = invalidateDelegate; + mBgColor = Themes.getAttrColor(launcher, android.R.attr.colorPrimary); + + DeviceProfile grid = launcher.getDeviceProfile(); + final int previewSize = grid.folderIconSizePx; + final int previewPadding = grid.folderIconPreviewPadding; + + this.previewSize = (previewSize - 2 * previewPadding); + + basePreviewOffsetX = (availableSpace - this.previewSize) / 2; + basePreviewOffsetY = previewPadding + grid.folderBackgroundOffset + topPadding; + + // Stroke width is 1dp + mStrokeWidth = launcher.getResources().getDisplayMetrics().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(); + } + + int getRadius() { + return previewSize / 2; + } + + int getScaledRadius() { + return (int) (mScale * getRadius()); + } + + int getOffsetX() { + return basePreviewOffsetX - (getScaledRadius() - getRadius()); + } + + int getOffsetY() { + 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() { + if (mInvalidateDelegate != null) { + mInvalidateDelegate.invalidate(); + } + + if (mDrawingDelegate != null) { + mDrawingDelegate.invalidate(); + } + } + + void setInvalidateDelegate(View invalidateDelegate) { + mInvalidateDelegate = invalidateDelegate; + invalidate(); + } + + public void drawBackground(Canvas canvas) { + mPaint.setStyle(Paint.Style.FILL); + int alpha = (int) Math.min(MAX_BG_OPACITY, BG_OPACITY * mColorMultiplier); + mPaint.setColor(ColorUtils.setAlphaComponent(mBgColor, alpha)); + + 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); + + } else { + saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG); + clipCanvasSoftware(canvas, Region.Op.DIFFERENCE); + } + + mShaderMatrix.setScale(shadowRadius, shadowRadius); + mShaderMatrix.postTranslate(radius + offsetX, shadowRadius + offsetY); + mShadowShader.setLocalMatrix(mShaderMatrix); + mPaint.setAlpha(mShadowAlpha); + mPaint.setShader(mShadowShader); + canvas.drawPaint(mPaint); + mPaint.setAlpha(255); + mPaint.setShader(null); + if (canvas.isHardwareAccelerated()) { + mPaint.setXfermode(mShadowPorterDuffXfermode); + canvas.drawCircle(radius + offsetX, radius + offsetY, radius, mPaint); + mPaint.setXfermode(null); + } + + canvas.restoreToCount(saveCount); + } + + public void fadeInBackgroundShadow() { + if (mShadowAnimator != null) { + mShadowAnimator.cancel(); + } + mShadowAnimator = ObjectAnimator + .ofInt(this, SHADOW_ALPHA, 0, 255) + .setDuration(100); + mShadowAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mShadowAnimator = null; + } + }); + mShadowAnimator.start(); + } + + public void animateBackgroundStroke() { + if (mStrokeAlphaAnimator != null) { + mStrokeAlphaAnimator.cancel(); + } + mStrokeAlphaAnimator = ObjectAnimator + .ofInt(this, STROKE_ALPHA, MAX_BG_OPACITY / 2, MAX_BG_OPACITY) + .setDuration(100); + mStrokeAlphaAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mStrokeAlphaAnimator = null; + } + }); + mStrokeAlphaAnimator.start(); + } + + public void drawBackgroundStroke(Canvas canvas) { + mPaint.setColor(ColorUtils.setAlphaComponent(mBgColor, mStrokeAlpha)); + mPaint.setStyle(Paint.Style.STROKE); + mPaint.setStrokeWidth(mStrokeWidth); + drawCircle(canvas, 1 /* deltaRadius */); + } + + public void drawLeaveBehind(Canvas canvas) { + float originalScale = mScale; + mScale = 0.5f; + + mPaint.setStyle(Paint.Style.FILL); + mPaint.setColor(Color.argb(160, 245, 245, 245)); + drawCircle(canvas, 0 /* deltaRadius */); + + mScale = originalScale; + } + + 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. + 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. + 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) { + if (mDrawingDelegate != delegate) { + delegate.addFolderBackground(this); + } + + mDrawingDelegate = delegate; + delegateCellX = cellX; + delegateCellY = cellY; + + invalidate(); + } + + private void clearDrawingDelegate() { + if (mDrawingDelegate != null) { + mDrawingDelegate.removeFolderBackground(this); + } + + mDrawingDelegate = null; + invalidate(); + } + + boolean drawingDelegated() { + return mDrawingDelegate != null; + } + + private void animateScale(float finalScale, float finalMultiplier, + final Runnable onStart, final Runnable onEnd) { + final float scale0 = mScale; + final float scale1 = finalScale; + + final float bgMultiplier0 = mColorMultiplier; + final float bgMultiplier1 = finalMultiplier; + + if (mScaleAnimator != null) { + mScaleAnimator.cancel(); + } + + mScaleAnimator = LauncherAnimUtils.ofFloat(0f, 1.0f); + + mScaleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float prog = animation.getAnimatedFraction(); + mScale = prog * scale1 + (1 - prog) * scale0; + mColorMultiplier = prog * bgMultiplier1 + (1 - prog) * bgMultiplier0; + invalidate(); + } + }); + mScaleAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + if (onStart != null) { + onStart.run(); + } + } + + @Override + public void onAnimationEnd(Animator animation) { + if (onEnd != null) { + onEnd.run(); + } + mScaleAnimator = null; + } + }); + + mScaleAnimator.setDuration(CONSUMPTION_ANIMATION_DURATION); + mScaleAnimator.start(); + } + + public void animateToAccept(final CellLayout cl, final int cellX, final int cellY) { + Runnable onStart = new Runnable() { + @Override + public void run() { + delegateDrawing(cl, cellX, cellY); + } + }; + animateScale(ACCEPT_SCALE_FACTOR, ACCEPT_COLOR_MULTIPLIER, onStart, null); + } + + public void animateToRest() { + // This can be called multiple times -- we need to make sure the drawing delegate + // is saved and restored at the beginning of the animation, since cancelling the + // existing animation can clear the delgate. + final CellLayout cl = mDrawingDelegate; + final int cellX = delegateCellX; + final int cellY = delegateCellY; + + Runnable onStart = new Runnable() { + @Override + public void run() { + delegateDrawing(cl, cellX, cellY); + } + }; + Runnable onEnd = new Runnable() { + @Override + public void run() { + clearDrawingDelegate(); + } + }; + animateScale(1f, 1f, onStart, onEnd); + } + + public int getBackgroundAlpha() { + return (int) Math.min(MAX_BG_OPACITY, BG_OPACITY * mColorMultiplier); + } + + public float getStrokeWidth() { + return mStrokeWidth; + } +} |