• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 
18 package com.android.launcher3.graphics;
19 
20 import android.animation.Animator;
21 import android.animation.AnimatorListenerAdapter;
22 import android.animation.ObjectAnimator;
23 import android.content.Context;
24 import android.graphics.Bitmap;
25 import android.graphics.Canvas;
26 import android.graphics.Matrix;
27 import android.graphics.Paint;
28 import android.graphics.Path;
29 import android.graphics.PathMeasure;
30 import android.graphics.Rect;
31 import android.util.Property;
32 import android.util.SparseArray;
33 import android.view.animation.LinearInterpolator;
34 
35 import com.android.launcher3.FastBitmapDrawable;
36 
37 import java.lang.ref.WeakReference;
38 
39 /**
40  * Extension of {@link FastBitmapDrawable} which shows a progress bar around the icon.
41  */
42 public class PreloadIconDrawable extends FastBitmapDrawable {
43 
44     private static final Property<PreloadIconDrawable, Float> INTERNAL_STATE =
45             new Property<PreloadIconDrawable, Float>(Float.TYPE, "internalStateProgress") {
46                 @Override
47                 public Float get(PreloadIconDrawable object) {
48                     return object.mInternalStateProgress;
49                 }
50 
51                 @Override
52                 public void set(PreloadIconDrawable object, Float value) {
53                     object.setInternalProgress(value);
54                 }
55             };
56 
57     public static final int PATH_SIZE = 100;
58 
59     private static final float PROGRESS_WIDTH = 7;
60     private static final float PROGRESS_GAP = 2;
61     private static final int MAX_PAINT_ALPHA = 255;
62 
63     private static final long DURATION_SCALE = 500;
64 
65     // The smaller the number, the faster the animation would be.
66     // Duration = COMPLETE_ANIM_FRACTION * DURATION_SCALE
67     private static final float COMPLETE_ANIM_FRACTION = 0.3f;
68 
69     private static final int COLOR_TRACK = 0x77EEEEEE;
70     private static final int COLOR_SHADOW = 0x55000000;
71 
72     private static final float SMALL_SCALE = 0.75f;
73 
74     private static final SparseArray<WeakReference<Bitmap>> sShadowCache = new SparseArray<>();
75 
76     private final Matrix mTmpMatrix = new Matrix();
77     private final PathMeasure mPathMeasure = new PathMeasure();
78 
79     private final Context mContext;
80 
81     // Path in [0, 100] bounds.
82     private final Path mProgressPath;
83 
84     private final Path mScaledTrackPath;
85     private final Path mScaledProgressPath;
86     private final Paint mProgressPaint;
87 
88     private Bitmap mShadowBitmap;
89     private int mIndicatorColor = 0;
90 
91     private int mTrackAlpha;
92     private float mTrackLength;
93     private float mIconScale;
94 
95     private boolean mRanFinishAnimation;
96 
97     // Progress of the internal state. [0, 1] indicates the fraction of completed progress,
98     // [1, (1 + COMPLETE_ANIM_FRACTION)] indicates the progress of zoom animation.
99     private float mInternalStateProgress;
100 
101     private ObjectAnimator mCurrentAnim;
102 
103     /**
104      * @param progressPath fixed path in the bounds [0, 0, 100, 100] representing a progress bar.
105      */
PreloadIconDrawable(Bitmap b, Path progressPath, Context context)106     public PreloadIconDrawable(Bitmap b, Path progressPath, Context context) {
107         super(b);
108         mContext = context;
109         mProgressPath = progressPath;
110         mScaledTrackPath = new Path();
111         mScaledProgressPath = new Path();
112 
113         mProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
114         mProgressPaint.setStyle(Paint.Style.STROKE);
115         mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
116 
117         setInternalProgress(0);
118     }
119 
120     @Override
onBoundsChange(Rect bounds)121     protected void onBoundsChange(Rect bounds) {
122         super.onBoundsChange(bounds);
123         mTmpMatrix.setScale(
124                 (bounds.width() - PROGRESS_WIDTH - 2 * PROGRESS_GAP) / PATH_SIZE,
125                 (bounds.height() - PROGRESS_WIDTH - 2 * PROGRESS_GAP) / PATH_SIZE);
126         mTmpMatrix.postTranslate(
127                 bounds.left + PROGRESS_WIDTH / 2 + PROGRESS_GAP,
128                 bounds.top + PROGRESS_WIDTH / 2 + PROGRESS_GAP);
129 
130         mProgressPath.transform(mTmpMatrix, mScaledTrackPath);
131         float scale = bounds.width() / PATH_SIZE;
132         mProgressPaint.setStrokeWidth(PROGRESS_WIDTH * scale);
133 
134         mShadowBitmap = getShadowBitmap(bounds.width(), bounds.height(),
135                 (PROGRESS_GAP ) * scale);
136         mPathMeasure.setPath(mScaledTrackPath, true);
137         mTrackLength = mPathMeasure.getLength();
138 
139         setInternalProgress(mInternalStateProgress);
140     }
141 
getShadowBitmap(int width, int height, float shadowRadius)142     private Bitmap getShadowBitmap(int width, int height, float shadowRadius) {
143         int key = (width << 16) | height;
144         WeakReference<Bitmap> shadowRef = sShadowCache.get(key);
145         Bitmap shadow = shadowRef != null ? shadowRef.get() : null;
146         if (shadow != null) {
147             return shadow;
148         }
149         shadow = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
150         Canvas c = new Canvas(shadow);
151         mProgressPaint.setShadowLayer(shadowRadius, 0, 0, COLOR_SHADOW);
152         mProgressPaint.setColor(COLOR_TRACK);
153         mProgressPaint.setAlpha(MAX_PAINT_ALPHA);
154         c.drawPath(mScaledTrackPath, mProgressPaint);
155         mProgressPaint.clearShadowLayer();
156         c.setBitmap(null);
157 
158         sShadowCache.put(key, new WeakReference<>(shadow));
159         return shadow;
160     }
161 
162     @Override
draw(Canvas canvas)163     public void draw(Canvas canvas) {
164         if (mRanFinishAnimation) {
165             super.draw(canvas);
166             return;
167         }
168 
169         // Draw track.
170         mProgressPaint.setColor(mIndicatorColor);
171         mProgressPaint.setAlpha(mTrackAlpha);
172         if (mShadowBitmap != null) {
173             canvas.drawBitmap(mShadowBitmap, getBounds().left, getBounds().top, mProgressPaint);
174         }
175         canvas.drawPath(mScaledProgressPath, mProgressPaint);
176 
177         int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
178         Rect bounds = getBounds();
179 
180         canvas.scale(mIconScale, mIconScale, bounds.exactCenterX(), bounds.exactCenterY());
181         drawInternal(canvas);
182         canvas.restoreToCount(saveCount);
183     }
184 
185     /**
186      * Updates the install progress based on the level
187      */
188     @Override
onLevelChange(int level)189     protected boolean onLevelChange(int level) {
190         // Run the animation if we have already been bound.
191         updateInternalState(level * 0.01f,  getBounds().width() > 0, false);
192         return true;
193     }
194 
195     /**
196      * Runs the finish animation if it is has not been run after last call to
197      * {@link #onLevelChange}
198      */
maybePerformFinishedAnimation()199     public void maybePerformFinishedAnimation() {
200         // If the drawable was recently initialized, skip the progress animation.
201         if (mInternalStateProgress == 0) {
202             mInternalStateProgress = 1;
203         }
204         updateInternalState(1 + COMPLETE_ANIM_FRACTION, true, true);
205     }
206 
hasNotCompleted()207     public boolean hasNotCompleted() {
208         return !mRanFinishAnimation;
209     }
210 
updateInternalState(float finalProgress, boolean shouldAnimate, boolean isFinish)211     private void updateInternalState(float finalProgress, boolean shouldAnimate, boolean isFinish) {
212         if (mCurrentAnim != null) {
213             mCurrentAnim.cancel();
214             mCurrentAnim = null;
215         }
216 
217         if (Float.compare(finalProgress, mInternalStateProgress) == 0) {
218             return;
219         }
220         if (finalProgress < mInternalStateProgress) {
221             shouldAnimate = false;
222         }
223         if (!shouldAnimate || mRanFinishAnimation) {
224             setInternalProgress(finalProgress);
225         } else {
226             mCurrentAnim = ObjectAnimator.ofFloat(this, INTERNAL_STATE, finalProgress);
227             mCurrentAnim.setDuration(
228                     (long) ((finalProgress - mInternalStateProgress) * DURATION_SCALE));
229             mCurrentAnim.setInterpolator(new LinearInterpolator());
230             if (isFinish) {
231                 mCurrentAnim.addListener(new AnimatorListenerAdapter() {
232                     @Override
233                     public void onAnimationEnd(Animator animation) {
234                         mRanFinishAnimation = true;
235                     }
236                 });
237             }
238             mCurrentAnim.start();
239         }
240 
241     }
242 
243     /**
244      * Sets the internal progress and updates the UI accordingly
245      *   for progress <= 0:
246      *     - icon in the small scale and disabled state
247      *     - progress track is visible
248      *     - progress bar is not visible
249      *   for 0 < progress < 1
250      *     - icon in the small scale and disabled state
251      *     - progress track is visible
252      *     - progress bar is visible with dominant color. Progress bar is drawn as a fraction of
253      *       {@link #mScaledTrackPath}.
254      *       @see PathMeasure#getSegment(float, float, Path, boolean)
255      *   for 1 <= progress < (1 + COMPLETE_ANIM_FRACTION)
256      *     - we calculate fraction of progress in the above range
257      *     - progress track is drawn with alpha based on fraction
258      *     - progress bar is drawn at 100% with alpha based on fraction
259      *     - icon is scaled up based on fraction and is drawn in enabled state
260      *   for progress >= (1 + COMPLETE_ANIM_FRACTION)
261      *     - only icon is drawn in normal state
262      */
setInternalProgress(float progress)263     private void setInternalProgress(float progress) {
264         mInternalStateProgress = progress;
265         if (progress <= 0) {
266             mIconScale = SMALL_SCALE;
267             mScaledTrackPath.reset();
268             mTrackAlpha = MAX_PAINT_ALPHA;
269             setIsDisabled(true);
270         } else if (mIndicatorColor == 0) {
271             // Update the indicator color
272             mIndicatorColor = getIconPalette().getPreloadProgressColor(mContext);
273         }
274 
275         if (progress < 1 && progress > 0) {
276             mPathMeasure.getSegment(0, progress * mTrackLength, mScaledProgressPath, true);
277             mIconScale = SMALL_SCALE;
278             mTrackAlpha = MAX_PAINT_ALPHA;
279             setIsDisabled(true);
280         } else if (progress >= 1) {
281             setIsDisabled(false);
282             mScaledTrackPath.set(mScaledProgressPath);
283             float fraction = (progress - 1) / COMPLETE_ANIM_FRACTION;
284 
285             if (fraction >= 1) {
286                 // Animation has completed
287                 mIconScale = 1;
288                 mTrackAlpha = 0;
289             } else {
290                 mTrackAlpha = Math.round((1 - fraction) * MAX_PAINT_ALPHA);
291                 mIconScale = SMALL_SCALE + (1 - SMALL_SCALE) * fraction;
292             }
293         }
294         invalidateSelf();
295     }
296 }
297