blob: 3c9e2dc2044b9d9ff8b24abf7f64ea0dfa0ad4a4 [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;
Sunny Goyalab770a12018-11-14 15:17:26 -080026import android.content.Context;
Jon Mirandaa0233f72017-06-22 18:34:45 -070027import android.graphics.Canvas;
28import android.graphics.Color;
29import android.graphics.Matrix;
30import android.graphics.Paint;
31import android.graphics.Path;
32import android.graphics.PorterDuff;
33import android.graphics.PorterDuffXfermode;
34import android.graphics.RadialGradient;
35import android.graphics.Region;
36import android.graphics.Shader;
Jon Mirandaa0233f72017-06-22 18:34:45 -070037import android.util.Property;
38import android.view.View;
39
40import com.android.launcher3.CellLayout;
41import com.android.launcher3.DeviceProfile;
42import com.android.launcher3.Launcher;
Tony05d98c22018-07-23 08:01:15 -070043import com.android.launcher3.R;
Jon Mirandaa0233f72017-06-22 18:34:45 -070044import com.android.launcher3.util.Themes;
Sunny Goyalab770a12018-11-14 15:17:26 -080045import com.android.launcher3.views.ActivityContext;
Jon Mirandaa0233f72017-06-22 18:34:45 -070046
47/**
48 * This object represents a FolderIcon preview background. It stores drawing / measurement
49 * information, handles drawing, and animation (accept state <--> rest state).
50 */
51public class PreviewBackground {
52
53 private static final int CONSUMPTION_ANIMATION_DURATION = 100;
54
Jon Mirandaa0233f72017-06-22 18:34:45 -070055 private final PorterDuffXfermode mShadowPorterDuffXfermode
56 = new PorterDuffXfermode(PorterDuff.Mode.DST_OUT);
57 private RadialGradient mShadowShader = null;
58
59 private final Matrix mShaderMatrix = new Matrix();
60 private final Path mPath = new Path();
61
62 private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
63
64 float mScale = 1f;
65 private float mColorMultiplier = 1f;
66 private int mBgColor;
Tony05d98c22018-07-23 08:01:15 -070067 private int mBadgeColor;
Jon Mirandaa0233f72017-06-22 18:34:45 -070068 private float mStrokeWidth;
69 private int mStrokeAlpha = MAX_BG_OPACITY;
70 private int mShadowAlpha = 255;
71 private View mInvalidateDelegate;
72
73 int previewSize;
74 int basePreviewOffsetX;
75 int basePreviewOffsetY;
76
77 private CellLayout mDrawingDelegate;
78 public int delegateCellX;
79 public int delegateCellY;
80
81 // When the PreviewBackground is drawn under an icon (for creating a folder) the border
82 // should not occlude the icon
83 public boolean isClipping = true;
84
85 // Drawing / animation configurations
Jon Miranda72b5fd12017-06-25 18:06:23 -070086 private static final float ACCEPT_SCALE_FACTOR = 1.20f;
Jon Mirandaa0233f72017-06-22 18:34:45 -070087 private static final float ACCEPT_COLOR_MULTIPLIER = 1.5f;
88
89 // Expressed on a scale from 0 to 255.
90 private static final int BG_OPACITY = 160;
91 private static final int MAX_BG_OPACITY = 225;
92 private static final int SHADOW_OPACITY = 40;
93
94 private ValueAnimator mScaleAnimator;
95 private ObjectAnimator mStrokeAlphaAnimator;
96 private ObjectAnimator mShadowAnimator;
97
98 private static final Property<PreviewBackground, Integer> STROKE_ALPHA =
99 new Property<PreviewBackground, Integer>(Integer.class, "strokeAlpha") {
100 @Override
101 public Integer get(PreviewBackground previewBackground) {
102 return previewBackground.mStrokeAlpha;
103 }
104
105 @Override
106 public void set(PreviewBackground previewBackground, Integer alpha) {
107 previewBackground.mStrokeAlpha = alpha;
108 previewBackground.invalidate();
109 }
110 };
111
112 private static final Property<PreviewBackground, Integer> SHADOW_ALPHA =
113 new Property<PreviewBackground, Integer>(Integer.class, "shadowAlpha") {
114 @Override
115 public Integer get(PreviewBackground previewBackground) {
116 return previewBackground.mShadowAlpha;
117 }
118
119 @Override
120 public void set(PreviewBackground previewBackground, Integer alpha) {
121 previewBackground.mShadowAlpha = alpha;
122 previewBackground.invalidate();
123 }
124 };
125
Sunny Goyalab770a12018-11-14 15:17:26 -0800126 public void setup(Context context, ActivityContext activity, View invalidateDelegate,
Jon Miranda591e3602018-03-28 11:37:00 -0700127 int availableSpaceX, int topPadding) {
Jon Mirandaa0233f72017-06-22 18:34:45 -0700128 mInvalidateDelegate = invalidateDelegate;
Sunny Goyalab770a12018-11-14 15:17:26 -0800129 mBgColor = Themes.getAttrColor(context, android.R.attr.colorPrimary);
130 mBadgeColor = Themes.getAttrColor(context, R.attr.folderBadgeColor);
Jon Mirandaa0233f72017-06-22 18:34:45 -0700131
Sunny Goyalab770a12018-11-14 15:17:26 -0800132 DeviceProfile grid = activity.getDeviceProfile();
Jon Miranda591e3602018-03-28 11:37:00 -0700133 previewSize = grid.folderIconSizePx;
Jon Mirandaa0233f72017-06-22 18:34:45 -0700134
Jon Miranda591e3602018-03-28 11:37:00 -0700135 basePreviewOffsetX = (availableSpaceX - previewSize) / 2;
136 basePreviewOffsetY = topPadding + grid.folderIconOffsetYPx;
Jon Mirandaa0233f72017-06-22 18:34:45 -0700137
138 // Stroke width is 1dp
Sunny Goyalab770a12018-11-14 15:17:26 -0800139 mStrokeWidth = context.getResources().getDisplayMetrics().density;
Jon Mirandaa0233f72017-06-22 18:34:45 -0700140
141 float radius = getScaledRadius();
142 float shadowRadius = radius + mStrokeWidth;
143 int shadowColor = Color.argb(SHADOW_OPACITY, 0, 0, 0);
144 mShadowShader = new RadialGradient(0, 0, 1,
145 new int[] {shadowColor, Color.TRANSPARENT},
146 new float[] {radius / shadowRadius, 1},
147 Shader.TileMode.CLAMP);
148
149 invalidate();
150 }
151
152 int getRadius() {
153 return previewSize / 2;
154 }
155
156 int getScaledRadius() {
157 return (int) (mScale * getRadius());
158 }
159
160 int getOffsetX() {
161 return basePreviewOffsetX - (getScaledRadius() - getRadius());
162 }
163
164 int getOffsetY() {
165 return basePreviewOffsetY - (getScaledRadius() - getRadius());
166 }
167
168 /**
169 * Returns the progress of the scale animation, where 0 means the scale is at 1f
170 * and 1 means the scale is at ACCEPT_SCALE_FACTOR.
171 */
172 float getScaleProgress() {
173 return (mScale - 1f) / (ACCEPT_SCALE_FACTOR - 1f);
174 }
175
176 void invalidate() {
177 if (mInvalidateDelegate != null) {
178 mInvalidateDelegate.invalidate();
179 }
180
181 if (mDrawingDelegate != null) {
182 mDrawingDelegate.invalidate();
183 }
184 }
185
186 void setInvalidateDelegate(View invalidateDelegate) {
187 mInvalidateDelegate = invalidateDelegate;
188 invalidate();
189 }
190
Sunny Goyale29897f2017-07-20 10:09:42 -0700191 public int getBgColor() {
192 int alpha = (int) Math.min(MAX_BG_OPACITY, BG_OPACITY * mColorMultiplier);
Sunny Goyal066ace12018-11-05 11:08:31 -0800193 return setColorAlphaBound(mBgColor, alpha);
Sunny Goyale29897f2017-07-20 10:09:42 -0700194 }
195
Sunny Goyal179249d2017-12-19 16:49:24 -0800196 public int getBadgeColor() {
Tony05d98c22018-07-23 08:01:15 -0700197 return mBadgeColor;
Sunny Goyal179249d2017-12-19 16:49:24 -0800198 }
199
Jon Mirandaa0233f72017-06-22 18:34:45 -0700200 public void drawBackground(Canvas canvas) {
201 mPaint.setStyle(Paint.Style.FILL);
Sunny Goyale29897f2017-07-20 10:09:42 -0700202 mPaint.setColor(getBgColor());
Jon Mirandaa0233f72017-06-22 18:34:45 -0700203
Sunny Goyald0ae4922018-10-11 10:37:53 -0700204 getShape().drawShape(canvas, getOffsetX(), getOffsetY(), getScaledRadius(), mPaint);
Sunny Goyale29897f2017-07-20 10:09:42 -0700205 drawShadow(canvas);
206 }
207
208 public void drawShadow(Canvas canvas) {
Jon Mirandaa0233f72017-06-22 18:34:45 -0700209 if (mShadowShader == null) {
210 return;
211 }
Sunny Goyale29897f2017-07-20 10:09:42 -0700212
Jon Mirandaa0233f72017-06-22 18:34:45 -0700213 float radius = getScaledRadius();
214 float shadowRadius = radius + mStrokeWidth;
Sunny Goyale29897f2017-07-20 10:09:42 -0700215 mPaint.setStyle(Paint.Style.FILL);
Jon Mirandaa0233f72017-06-22 18:34:45 -0700216 mPaint.setColor(Color.BLACK);
217 int offsetX = getOffsetX();
218 int offsetY = getOffsetY();
219 final int saveCount;
220 if (canvas.isHardwareAccelerated()) {
221 saveCount = canvas.saveLayer(offsetX - mStrokeWidth, offsetY,
Derek Sollenberger26a1b452018-02-23 13:37:25 -0500222 offsetX + radius + shadowRadius, offsetY + shadowRadius + shadowRadius, null);
Jon Mirandaa0233f72017-06-22 18:34:45 -0700223
224 } else {
Derek Sollenberger26a1b452018-02-23 13:37:25 -0500225 saveCount = canvas.save();
Sunny Goyale29897f2017-07-20 10:09:42 -0700226 canvas.clipPath(getClipPath(), Region.Op.DIFFERENCE);
Jon Mirandaa0233f72017-06-22 18:34:45 -0700227 }
228
229 mShaderMatrix.setScale(shadowRadius, shadowRadius);
230 mShaderMatrix.postTranslate(radius + offsetX, shadowRadius + offsetY);
231 mShadowShader.setLocalMatrix(mShaderMatrix);
232 mPaint.setAlpha(mShadowAlpha);
233 mPaint.setShader(mShadowShader);
234 canvas.drawPaint(mPaint);
235 mPaint.setAlpha(255);
236 mPaint.setShader(null);
237 if (canvas.isHardwareAccelerated()) {
238 mPaint.setXfermode(mShadowPorterDuffXfermode);
Sunny Goyald0ae4922018-10-11 10:37:53 -0700239 getShape().drawShape(canvas, offsetX, offsetY, radius, mPaint);
Jon Mirandaa0233f72017-06-22 18:34:45 -0700240 mPaint.setXfermode(null);
241 }
242
243 canvas.restoreToCount(saveCount);
244 }
245
246 public void fadeInBackgroundShadow() {
247 if (mShadowAnimator != null) {
248 mShadowAnimator.cancel();
249 }
250 mShadowAnimator = ObjectAnimator
251 .ofInt(this, SHADOW_ALPHA, 0, 255)
252 .setDuration(100);
253 mShadowAnimator.addListener(new AnimatorListenerAdapter() {
254 @Override
255 public void onAnimationEnd(Animator animation) {
256 mShadowAnimator = null;
257 }
258 });
259 mShadowAnimator.start();
260 }
261
262 public void animateBackgroundStroke() {
263 if (mStrokeAlphaAnimator != null) {
264 mStrokeAlphaAnimator.cancel();
265 }
266 mStrokeAlphaAnimator = ObjectAnimator
267 .ofInt(this, STROKE_ALPHA, MAX_BG_OPACITY / 2, MAX_BG_OPACITY)
268 .setDuration(100);
269 mStrokeAlphaAnimator.addListener(new AnimatorListenerAdapter() {
270 @Override
271 public void onAnimationEnd(Animator animation) {
272 mStrokeAlphaAnimator = null;
273 }
274 });
275 mStrokeAlphaAnimator.start();
276 }
277
278 public void drawBackgroundStroke(Canvas canvas) {
Sunny Goyal066ace12018-11-05 11:08:31 -0800279 mPaint.setColor(setColorAlphaBound(mBgColor, mStrokeAlpha));
Jon Mirandaa0233f72017-06-22 18:34:45 -0700280 mPaint.setStyle(Paint.Style.STROKE);
281 mPaint.setStrokeWidth(mStrokeWidth);
Sunny Goyald0ae4922018-10-11 10:37:53 -0700282
283 float inset = 1f;
284 getShape().drawShape(canvas,
285 getOffsetX() + inset, getOffsetY() + inset, getScaledRadius() - inset, mPaint);
Jon Mirandaa0233f72017-06-22 18:34:45 -0700286 }
287
288 public void drawLeaveBehind(Canvas canvas) {
289 float originalScale = mScale;
290 mScale = 0.5f;
291
292 mPaint.setStyle(Paint.Style.FILL);
293 mPaint.setColor(Color.argb(160, 245, 245, 245));
Sunny Goyald0ae4922018-10-11 10:37:53 -0700294 getShape().drawShape(canvas, getOffsetX(), getOffsetY(), getScaledRadius(), mPaint);
Jon Mirandaa0233f72017-06-22 18:34:45 -0700295
296 mScale = originalScale;
297 }
298
Sunny Goyale29897f2017-07-20 10:09:42 -0700299 public Path getClipPath() {
Jon Mirandaa0233f72017-06-22 18:34:45 -0700300 mPath.reset();
Sunny Goyald0ae4922018-10-11 10:37:53 -0700301 getShape().addShape(mPath, getOffsetX(), getOffsetY(), getScaledRadius());
Sunny Goyale29897f2017-07-20 10:09:42 -0700302 return mPath;
Jon Mirandaa0233f72017-06-22 18:34:45 -0700303 }
304
Jon Mirandaa0233f72017-06-22 18:34:45 -0700305 private void delegateDrawing(CellLayout delegate, int cellX, int cellY) {
306 if (mDrawingDelegate != delegate) {
307 delegate.addFolderBackground(this);
308 }
309
310 mDrawingDelegate = delegate;
311 delegateCellX = cellX;
312 delegateCellY = cellY;
313
314 invalidate();
315 }
316
317 private void clearDrawingDelegate() {
318 if (mDrawingDelegate != null) {
319 mDrawingDelegate.removeFolderBackground(this);
320 }
321
322 mDrawingDelegate = null;
Sunny Goyal5247f5b2017-07-04 12:07:46 -0700323 isClipping = true;
Jon Mirandaa0233f72017-06-22 18:34:45 -0700324 invalidate();
325 }
326
327 boolean drawingDelegated() {
328 return mDrawingDelegate != null;
329 }
330
331 private void animateScale(float finalScale, float finalMultiplier,
332 final Runnable onStart, final Runnable onEnd) {
333 final float scale0 = mScale;
334 final float scale1 = finalScale;
335
336 final float bgMultiplier0 = mColorMultiplier;
337 final float bgMultiplier1 = finalMultiplier;
338
339 if (mScaleAnimator != null) {
340 mScaleAnimator.cancel();
341 }
342
Sunny Goyal849c6a22018-08-08 16:33:46 -0700343 mScaleAnimator = ValueAnimator.ofFloat(0f, 1.0f);
Jon Mirandaa0233f72017-06-22 18:34:45 -0700344
345 mScaleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
346 @Override
347 public void onAnimationUpdate(ValueAnimator animation) {
348 float prog = animation.getAnimatedFraction();
349 mScale = prog * scale1 + (1 - prog) * scale0;
350 mColorMultiplier = prog * bgMultiplier1 + (1 - prog) * bgMultiplier0;
351 invalidate();
352 }
353 });
354 mScaleAnimator.addListener(new AnimatorListenerAdapter() {
355 @Override
356 public void onAnimationStart(Animator animation) {
357 if (onStart != null) {
358 onStart.run();
359 }
360 }
361
362 @Override
363 public void onAnimationEnd(Animator animation) {
364 if (onEnd != null) {
365 onEnd.run();
366 }
367 mScaleAnimator = null;
368 }
369 });
370
371 mScaleAnimator.setDuration(CONSUMPTION_ANIMATION_DURATION);
372 mScaleAnimator.start();
373 }
374
Sunny Goyal849c6a22018-08-08 16:33:46 -0700375 public void animateToAccept(CellLayout cl, int cellX, int cellY) {
376 animateScale(ACCEPT_SCALE_FACTOR, ACCEPT_COLOR_MULTIPLIER,
377 () -> delegateDrawing(cl, cellX, cellY), null);
Jon Mirandaa0233f72017-06-22 18:34:45 -0700378 }
379
380 public void animateToRest() {
381 // This can be called multiple times -- we need to make sure the drawing delegate
382 // is saved and restored at the beginning of the animation, since cancelling the
383 // existing animation can clear the delgate.
Sunny Goyal849c6a22018-08-08 16:33:46 -0700384 CellLayout cl = mDrawingDelegate;
385 int cellX = delegateCellX;
386 int cellY = delegateCellY;
387 animateScale(1f, 1f, () -> delegateDrawing(cl, cellX, cellY), this::clearDrawingDelegate);
Jon Mirandaa0233f72017-06-22 18:34:45 -0700388 }
389
390 public int getBackgroundAlpha() {
391 return (int) Math.min(MAX_BG_OPACITY, BG_OPACITY * mColorMultiplier);
392 }
393
394 public float getStrokeWidth() {
395 return mStrokeWidth;
396 }
397}