From a0233f72bedd7d91cf2b6febec49fadf89a60c1f Mon Sep 17 00:00:00 2001 From: Jon Miranda Date: Thu, 22 Jun 2017 18:34:45 -0700 Subject: [PATCH] Move PreviewBackground into its own class file. This is done in preparation for new PreviewBackground animation where we will be animating in the first page preview items when closing the Folder from a non-first page. Bug: 36022592 Bug: 35064148 Change-Id: Id4d54da16ddfa852ca639a2dc8d328307d4d443a --- src/com/android/launcher3/CellLayout.java | 13 +- src/com/android/launcher3/Workspace.java | 7 +- .../folder/FolderAnimationManager.java | 2 +- .../android/launcher3/folder/FolderIcon.java | 401 ---------------- .../launcher3/folder/PreviewBackground.java | 430 ++++++++++++++++++ 5 files changed, 442 insertions(+), 411 deletions(-) create mode 100644 src/com/android/launcher3/folder/PreviewBackground.java diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index d0d33a03de..d07e3303db 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 mFolderBackgrounds = new ArrayList<>(); - final FolderIcon.PreviewBackground mFolderLeaveBehind = new FolderIcon.PreviewBackground(); + private final ArrayList 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 517073aca0..0fabeebbf5 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 74e8d3b298..cb86b59f31 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 fa148c889a..215a31c8f6 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 STROKE_ALPHA = - new Property(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 SHADOW_ALPHA = - new Property(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 0000000000..44ebbcda79 --- /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 STROKE_ALPHA = + new Property(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 SHADOW_ALPHA = + new Property(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; + } +}