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