summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/com/android/launcher3/CellLayout.java13
-rw-r--r--src/com/android/launcher3/Workspace.java7
-rw-r--r--src/com/android/launcher3/folder/FolderAnimationManager.java2
-rw-r--r--src/com/android/launcher3/folder/FolderIcon.java401
-rw-r--r--src/com/android/launcher3/folder/PreviewBackground.java430
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;
+ }
+}