Merge "Move PreviewBackground into its own class file." into ub-launcher3-dorval-polish
This commit is contained in:
commit
8a87856d3a
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue