blob: f2683a5f9aadd1d61e472916e73a7d826ebc9298 [file] [log] [blame]
Jon Mirandaa0233f72017-06-22 18:34:45 -07001/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.launcher3.folder;
18
Sunny Goyald0ae4922018-10-11 10:37:53 -070019import static com.android.launcher3.folder.FolderShape.getShape;
Sunny Goyal066ace12018-11-05 11:08:31 -080020import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
Sunny Goyald0ae4922018-10-11 10:37:53 -070021
Jon Mirandaa0233f72017-06-22 18:34:45 -070022import android.animation.Animator;
23import android.animation.AnimatorListenerAdapter;
24import android.animation.ObjectAnimator;
25import android.animation.ValueAnimator;
26import android.graphics.Canvas;
27import android.graphics.Color;
28import android.graphics.Matrix;
29import android.graphics.Paint;
30import android.graphics.Path;
31import android.graphics.PorterDuff;
32import android.graphics.PorterDuffXfermode;
33import android.graphics.RadialGradient;
34import android.graphics.Region;
35import android.graphics.Shader;
Jon Mirandaa0233f72017-06-22 18:34:45 -070036import android.util.Property;
37import android.view.View;
38
39import com.android.launcher3.CellLayout;
40import com.android.launcher3.DeviceProfile;
41import com.android.launcher3.Launcher;
Tony05d98c22018-07-23 08:01:15 -070042import com.android.launcher3.R;
Jon Mirandaa0233f72017-06-22 18:34:45 -070043import com.android.launcher3.util.Themes;
44
45/**
46 * This object represents a FolderIcon preview background. It stores drawing / measurement
47 * information, handles drawing, and animation (accept state <--> rest state).
48 */
49public class PreviewBackground {
50
51 private static final int CONSUMPTION_ANIMATION_DURATION = 100;
52
Jon Mirandaa0233f72017-06-22 18:34:45 -070053 private final PorterDuffXfermode mShadowPorterDuffXfermode
54 = new PorterDuffXfermode(PorterDuff.Mode.DST_OUT);
55 private RadialGradient mShadowShader = null;
56
57 private final Matrix mShaderMatrix = new Matrix();
58 private final Path mPath = new Path();
59
60 private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
61
62 float mScale = 1f;
63 private float mColorMultiplier = 1f;
64 private int mBgColor;
Tony05d98c22018-07-23 08:01:15 -070065 private int mBadgeColor;
Jon Mirandaa0233f72017-06-22 18:34:45 -070066 private float mStrokeWidth;
67 private int mStrokeAlpha = MAX_BG_OPACITY;
68 private int mShadowAlpha = 255;
69 private View mInvalidateDelegate;
70
71 int previewSize;
72 int basePreviewOffsetX;
73 int basePreviewOffsetY;
74
75 private CellLayout mDrawingDelegate;
76 public int delegateCellX;
77 public int delegateCellY;
78
79 // When the PreviewBackground is drawn under an icon (for creating a folder) the border
80 // should not occlude the icon
81 public boolean isClipping = true;
82
83 // Drawing / animation configurations
Jon Miranda72b5fd12017-06-25 18:06:23 -070084 private static final float ACCEPT_SCALE_FACTOR = 1.20f;
Jon Mirandaa0233f72017-06-22 18:34:45 -070085 private static final float ACCEPT_COLOR_MULTIPLIER = 1.5f;
86
87 // Expressed on a scale from 0 to 255.
88 private static final int BG_OPACITY = 160;
89 private static final int MAX_BG_OPACITY = 225;
90 private static final int SHADOW_OPACITY = 40;
91
92 private ValueAnimator mScaleAnimator;
93 private ObjectAnimator mStrokeAlphaAnimator;
94 private ObjectAnimator mShadowAnimator;
95
96 private static final Property<PreviewBackground, Integer> STROKE_ALPHA =
97 new Property<PreviewBackground, Integer>(Integer.class, "strokeAlpha") {
98 @Override
99 public Integer get(PreviewBackground previewBackground) {
100 return previewBackground.mStrokeAlpha;
101 }
102
103 @Override
104 public void set(PreviewBackground previewBackground, Integer alpha) {
105 previewBackground.mStrokeAlpha = alpha;
106 previewBackground.invalidate();
107 }
108 };
109
110 private static final Property<PreviewBackground, Integer> SHADOW_ALPHA =
111 new Property<PreviewBackground, Integer>(Integer.class, "shadowAlpha") {
112 @Override
113 public Integer get(PreviewBackground previewBackground) {
114 return previewBackground.mShadowAlpha;
115 }
116
117 @Override
118 public void set(PreviewBackground previewBackground, Integer alpha) {
119 previewBackground.mShadowAlpha = alpha;
120 previewBackground.invalidate();
121 }
122 };
123
124 public void setup(Launcher launcher, View invalidateDelegate,
Jon Miranda591e3602018-03-28 11:37:00 -0700125 int availableSpaceX, int topPadding) {
Jon Mirandaa0233f72017-06-22 18:34:45 -0700126 mInvalidateDelegate = invalidateDelegate;
127 mBgColor = Themes.getAttrColor(launcher, android.R.attr.colorPrimary);
Tony05d98c22018-07-23 08:01:15 -0700128 mBadgeColor = Themes.getAttrColor(launcher, R.attr.folderBadgeColor);
Jon Mirandaa0233f72017-06-22 18:34:45 -0700129
130 DeviceProfile grid = launcher.getDeviceProfile();
Jon Miranda591e3602018-03-28 11:37:00 -0700131 previewSize = grid.folderIconSizePx;
Jon Mirandaa0233f72017-06-22 18:34:45 -0700132
Jon Miranda591e3602018-03-28 11:37:00 -0700133 basePreviewOffsetX = (availableSpaceX - previewSize) / 2;
134 basePreviewOffsetY = topPadding + grid.folderIconOffsetYPx;
Jon Mirandaa0233f72017-06-22 18:34:45 -0700135
136 // Stroke width is 1dp
137 mStrokeWidth = launcher.getResources().getDisplayMetrics().density;
138
139 float radius = getScaledRadius();
140 float shadowRadius = radius + mStrokeWidth;
141 int shadowColor = Color.argb(SHADOW_OPACITY, 0, 0, 0);
142 mShadowShader = new RadialGradient(0, 0, 1,
143 new int[] {shadowColor, Color.TRANSPARENT},
144 new float[] {radius / shadowRadius, 1},
145 Shader.TileMode.CLAMP);
146
147 invalidate();
148 }
149
150 int getRadius() {
151 return previewSize / 2;
152 }
153
154 int getScaledRadius() {
155 return (int) (mScale * getRadius());
156 }
157
158 int getOffsetX() {
159 return basePreviewOffsetX - (getScaledRadius() - getRadius());
160 }
161
162 int getOffsetY() {
163 return basePreviewOffsetY - (getScaledRadius() - getRadius());
164 }
165
166 /**
167 * Returns the progress of the scale animation, where 0 means the scale is at 1f
168 * and 1 means the scale is at ACCEPT_SCALE_FACTOR.
169 */
170 float getScaleProgress() {
171 return (mScale - 1f) / (ACCEPT_SCALE_FACTOR - 1f);
172 }
173
174 void invalidate() {
175 if (mInvalidateDelegate != null) {
176 mInvalidateDelegate.invalidate();
177 }
178
179 if (mDrawingDelegate != null) {
180 mDrawingDelegate.invalidate();
181 }
182 }
183
184 void setInvalidateDelegate(View invalidateDelegate) {
185 mInvalidateDelegate = invalidateDelegate;
186 invalidate();
187 }
188
Sunny Goyale29897f2017-07-20 10:09:42 -0700189 public int getBgColor() {
190 int alpha = (int) Math.min(MAX_BG_OPACITY, BG_OPACITY * mColorMultiplier);
Sunny Goyal066ace12018-11-05 11:08:31 -0800191 return setColorAlphaBound(mBgColor, alpha);
Sunny Goyale29897f2017-07-20 10:09:42 -0700192 }
193
Sunny Goyal179249d2017-12-19 16:49:24 -0800194 public int getBadgeColor() {
Tony05d98c22018-07-23 08:01:15 -0700195 return mBadgeColor;
Sunny Goyal179249d2017-12-19 16:49:24 -0800196 }
197
Jon Mirandaa0233f72017-06-22 18:34:45 -0700198 public void drawBackground(Canvas canvas) {
199 mPaint.setStyle(Paint.Style.FILL);
Sunny Goyale29897f2017-07-20 10:09:42 -0700200 mPaint.setColor(getBgColor());
Jon Mirandaa0233f72017-06-22 18:34:45 -0700201
Sunny Goyald0ae4922018-10-11 10:37:53 -0700202 getShape().drawShape(canvas, getOffsetX(), getOffsetY(), getScaledRadius(), mPaint);
Sunny Goyale29897f2017-07-20 10:09:42 -0700203 drawShadow(canvas);
204 }
205
206 public void drawShadow(Canvas canvas) {
Jon Mirandaa0233f72017-06-22 18:34:45 -0700207 if (mShadowShader == null) {
208 return;
209 }
Sunny Goyale29897f2017-07-20 10:09:42 -0700210
Jon Mirandaa0233f72017-06-22 18:34:45 -0700211 float radius = getScaledRadius();
212 float shadowRadius = radius + mStrokeWidth;
Sunny Goyale29897f2017-07-20 10:09:42 -0700213 mPaint.setStyle(Paint.Style.FILL);
Jon Mirandaa0233f72017-06-22 18:34:45 -0700214 mPaint.setColor(Color.BLACK);
215 int offsetX = getOffsetX();
216 int offsetY = getOffsetY();
217 final int saveCount;
218 if (canvas.isHardwareAccelerated()) {
219 saveCount = canvas.saveLayer(offsetX - mStrokeWidth, offsetY,
Derek Sollenberger26a1b452018-02-23 13:37:25 -0500220 offsetX + radius + shadowRadius, offsetY + shadowRadius + shadowRadius, null);
Jon Mirandaa0233f72017-06-22 18:34:45 -0700221
222 } else {
Derek Sollenberger26a1b452018-02-23 13:37:25 -0500223 saveCount = canvas.save();
Sunny Goyale29897f2017-07-20 10:09:42 -0700224 canvas.clipPath(getClipPath(), Region.Op.DIFFERENCE);
Jon Mirandaa0233f72017-06-22 18:34:45 -0700225 }
226
227 mShaderMatrix.setScale(shadowRadius, shadowRadius);
228 mShaderMatrix.postTranslate(radius + offsetX, shadowRadius + offsetY);
229 mShadowShader.setLocalMatrix(mShaderMatrix);
230 mPaint.setAlpha(mShadowAlpha);
231 mPaint.setShader(mShadowShader);
232 canvas.drawPaint(mPaint);
233 mPaint.setAlpha(255);
234 mPaint.setShader(null);
235 if (canvas.isHardwareAccelerated()) {
236 mPaint.setXfermode(mShadowPorterDuffXfermode);
Sunny Goyald0ae4922018-10-11 10:37:53 -0700237 getShape().drawShape(canvas, offsetX, offsetY, radius, mPaint);
Jon Mirandaa0233f72017-06-22 18:34:45 -0700238 mPaint.setXfermode(null);
239 }
240
241 canvas.restoreToCount(saveCount);
242 }
243
244 public void fadeInBackgroundShadow() {
245 if (mShadowAnimator != null) {
246 mShadowAnimator.cancel();
247 }
248 mShadowAnimator = ObjectAnimator
249 .ofInt(this, SHADOW_ALPHA, 0, 255)
250 .setDuration(100);
251 mShadowAnimator.addListener(new AnimatorListenerAdapter() {
252 @Override
253 public void onAnimationEnd(Animator animation) {
254 mShadowAnimator = null;
255 }
256 });
257 mShadowAnimator.start();
258 }
259
260 public void animateBackgroundStroke() {
261 if (mStrokeAlphaAnimator != null) {
262 mStrokeAlphaAnimator.cancel();
263 }
264 mStrokeAlphaAnimator = ObjectAnimator
265 .ofInt(this, STROKE_ALPHA, MAX_BG_OPACITY / 2, MAX_BG_OPACITY)
266 .setDuration(100);
267 mStrokeAlphaAnimator.addListener(new AnimatorListenerAdapter() {
268 @Override
269 public void onAnimationEnd(Animator animation) {
270 mStrokeAlphaAnimator = null;
271 }
272 });
273 mStrokeAlphaAnimator.start();
274 }
275
276 public void drawBackgroundStroke(Canvas canvas) {
Sunny Goyal066ace12018-11-05 11:08:31 -0800277 mPaint.setColor(setColorAlphaBound(mBgColor, mStrokeAlpha));
Jon Mirandaa0233f72017-06-22 18:34:45 -0700278 mPaint.setStyle(Paint.Style.STROKE);
279 mPaint.setStrokeWidth(mStrokeWidth);
Sunny Goyald0ae4922018-10-11 10:37:53 -0700280
281 float inset = 1f;
282 getShape().drawShape(canvas,
283 getOffsetX() + inset, getOffsetY() + inset, getScaledRadius() - inset, mPaint);
Jon Mirandaa0233f72017-06-22 18:34:45 -0700284 }
285
286 public void drawLeaveBehind(Canvas canvas) {
287 float originalScale = mScale;
288 mScale = 0.5f;
289
290 mPaint.setStyle(Paint.Style.FILL);
291 mPaint.setColor(Color.argb(160, 245, 245, 245));
Sunny Goyald0ae4922018-10-11 10:37:53 -0700292 getShape().drawShape(canvas, getOffsetX(), getOffsetY(), getScaledRadius(), mPaint);
Jon Mirandaa0233f72017-06-22 18:34:45 -0700293
294 mScale = originalScale;
295 }
296
Sunny Goyale29897f2017-07-20 10:09:42 -0700297 public Path getClipPath() {
Jon Mirandaa0233f72017-06-22 18:34:45 -0700298 mPath.reset();
Sunny Goyald0ae4922018-10-11 10:37:53 -0700299 getShape().addShape(mPath, getOffsetX(), getOffsetY(), getScaledRadius());
Sunny Goyale29897f2017-07-20 10:09:42 -0700300 return mPath;
Jon Mirandaa0233f72017-06-22 18:34:45 -0700301 }
302
Jon Mirandaa0233f72017-06-22 18:34:45 -0700303 private void delegateDrawing(CellLayout delegate, int cellX, int cellY) {
304 if (mDrawingDelegate != delegate) {
305 delegate.addFolderBackground(this);
306 }
307
308 mDrawingDelegate = delegate;
309 delegateCellX = cellX;
310 delegateCellY = cellY;
311
312 invalidate();
313 }
314
315 private void clearDrawingDelegate() {
316 if (mDrawingDelegate != null) {
317 mDrawingDelegate.removeFolderBackground(this);
318 }
319
320 mDrawingDelegate = null;
Sunny Goyal5247f5b2017-07-04 12:07:46 -0700321 isClipping = true;
Jon Mirandaa0233f72017-06-22 18:34:45 -0700322 invalidate();
323 }
324
325 boolean drawingDelegated() {
326 return mDrawingDelegate != null;
327 }
328
329 private void animateScale(float finalScale, float finalMultiplier,
330 final Runnable onStart, final Runnable onEnd) {
331 final float scale0 = mScale;
332 final float scale1 = finalScale;
333
334 final float bgMultiplier0 = mColorMultiplier;
335 final float bgMultiplier1 = finalMultiplier;
336
337 if (mScaleAnimator != null) {
338 mScaleAnimator.cancel();
339 }
340
Sunny Goyal849c6a22018-08-08 16:33:46 -0700341 mScaleAnimator = ValueAnimator.ofFloat(0f, 1.0f);
Jon Mirandaa0233f72017-06-22 18:34:45 -0700342
343 mScaleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
344 @Override
345 public void onAnimationUpdate(ValueAnimator animation) {
346 float prog = animation.getAnimatedFraction();
347 mScale = prog * scale1 + (1 - prog) * scale0;
348 mColorMultiplier = prog * bgMultiplier1 + (1 - prog) * bgMultiplier0;
349 invalidate();
350 }
351 });
352 mScaleAnimator.addListener(new AnimatorListenerAdapter() {
353 @Override
354 public void onAnimationStart(Animator animation) {
355 if (onStart != null) {
356 onStart.run();
357 }
358 }
359
360 @Override
361 public void onAnimationEnd(Animator animation) {
362 if (onEnd != null) {
363 onEnd.run();
364 }
365 mScaleAnimator = null;
366 }
367 });
368
369 mScaleAnimator.setDuration(CONSUMPTION_ANIMATION_DURATION);
370 mScaleAnimator.start();
371 }
372
Sunny Goyal849c6a22018-08-08 16:33:46 -0700373 public void animateToAccept(CellLayout cl, int cellX, int cellY) {
374 animateScale(ACCEPT_SCALE_FACTOR, ACCEPT_COLOR_MULTIPLIER,
375 () -> delegateDrawing(cl, cellX, cellY), null);
Jon Mirandaa0233f72017-06-22 18:34:45 -0700376 }
377
378 public void animateToRest() {
379 // This can be called multiple times -- we need to make sure the drawing delegate
380 // is saved and restored at the beginning of the animation, since cancelling the
381 // existing animation can clear the delgate.
Sunny Goyal849c6a22018-08-08 16:33:46 -0700382 CellLayout cl = mDrawingDelegate;
383 int cellX = delegateCellX;
384 int cellY = delegateCellY;
385 animateScale(1f, 1f, () -> delegateDrawing(cl, cellX, cellY), this::clearDrawingDelegate);
Jon Mirandaa0233f72017-06-22 18:34:45 -0700386 }
387
388 public int getBackgroundAlpha() {
389 return (int) Math.min(MAX_BG_OPACITY, BG_OPACITY * mColorMultiplier);
390 }
391
392 public float getStrokeWidth() {
393 return mStrokeWidth;
394 }
395}