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 static com.android.launcher3.graphics.IconShape.DEFAULT_PATH_SIZE; 21 22 import android.animation.Animator; 23 import android.animation.AnimatorListenerAdapter; 24 import android.animation.ObjectAnimator; 25 import android.content.Context; 26 import android.graphics.Bitmap; 27 import android.graphics.Canvas; 28 import android.graphics.Matrix; 29 import android.graphics.Paint; 30 import android.graphics.Path; 31 import android.graphics.PathMeasure; 32 import android.graphics.Rect; 33 import android.util.Property; 34 import android.util.SparseArray; 35 36 import com.android.launcher3.FastBitmapDrawable; 37 import com.android.launcher3.ItemInfoWithIcon; 38 import com.android.launcher3.anim.Interpolators; 39 40 import java.lang.ref.WeakReference; 41 42 /** 43 * Extension of {@link FastBitmapDrawable} which shows a progress bar around the icon. 44 */ 45 public class PreloadIconDrawable extends FastBitmapDrawable { 46 47 private static final Property<PreloadIconDrawable, Float> INTERNAL_STATE = 48 new Property<PreloadIconDrawable, Float>(Float.TYPE, "internalStateProgress") { 49 @Override 50 public Float get(PreloadIconDrawable object) { 51 return object.mInternalStateProgress; 52 } 53 54 @Override 55 public void set(PreloadIconDrawable object, Float value) { 56 object.setInternalProgress(value); 57 } 58 }; 59 60 private static final float PROGRESS_WIDTH = 7; 61 private static final float PROGRESS_GAP = 2; 62 private static final int MAX_PAINT_ALPHA = 255; 63 64 private static final long DURATION_SCALE = 500; 65 66 // The smaller the number, the faster the animation would be. 67 // Duration = COMPLETE_ANIM_FRACTION * DURATION_SCALE 68 private static final float COMPLETE_ANIM_FRACTION = 0.3f; 69 70 private static final int COLOR_TRACK = 0x77EEEEEE; 71 private static final int COLOR_SHADOW = 0x55000000; 72 73 private static final float SMALL_SCALE = 0.6f; 74 75 private static final SparseArray<WeakReference<Bitmap>> sShadowCache = new SparseArray<>(); 76 77 private final Matrix mTmpMatrix = new Matrix(); 78 private final PathMeasure mPathMeasure = new PathMeasure(); 79 80 private final ItemInfoWithIcon mItem; 81 82 // Path in [0, 100] bounds. 83 private final Path mProgressPath; 84 85 private final Path mScaledTrackPath; 86 private final Path mScaledProgressPath; 87 private final Paint mProgressPaint; 88 89 private Bitmap mShadowBitmap; 90 private final int mIndicatorColor; 91 92 private int mTrackAlpha; 93 private float mTrackLength; 94 private float mIconScale; 95 96 private boolean mRanFinishAnimation; 97 98 // Progress of the internal state. [0, 1] indicates the fraction of completed progress, 99 // [1, (1 + COMPLETE_ANIM_FRACTION)] indicates the progress of zoom animation. 100 private float mInternalStateProgress; 101 102 private ObjectAnimator mCurrentAnim; 103 104 /** 105 * @param progressPath fixed path in the bounds [0, 0, 100, 100] representing a progress bar. 106 */ PreloadIconDrawable(ItemInfoWithIcon info, Path progressPath, Context context)107 public PreloadIconDrawable(ItemInfoWithIcon info, Path progressPath, Context context) { 108 super(info); 109 mItem = info; 110 mProgressPath = progressPath; 111 mScaledTrackPath = new Path(); 112 mScaledProgressPath = new Path(); 113 114 mProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); 115 mProgressPaint.setStyle(Paint.Style.STROKE); 116 mProgressPaint.setStrokeCap(Paint.Cap.ROUND); 117 mIndicatorColor = IconPalette.getPreloadProgressColor(context, mIconColor); 118 119 setInternalProgress(0); 120 } 121 122 @Override onBoundsChange(Rect bounds)123 protected void onBoundsChange(Rect bounds) { 124 super.onBoundsChange(bounds); 125 mTmpMatrix.setScale( 126 (bounds.width() - 2 * PROGRESS_WIDTH - 2 * PROGRESS_GAP) / DEFAULT_PATH_SIZE, 127 (bounds.height() - 2 * PROGRESS_WIDTH - 2 * PROGRESS_GAP) / DEFAULT_PATH_SIZE); 128 mTmpMatrix.postTranslate( 129 bounds.left + PROGRESS_WIDTH + PROGRESS_GAP, 130 bounds.top + PROGRESS_WIDTH + PROGRESS_GAP); 131 132 mProgressPath.transform(mTmpMatrix, mScaledTrackPath); 133 float scale = bounds.width() / DEFAULT_PATH_SIZE; 134 mProgressPaint.setStrokeWidth(PROGRESS_WIDTH * scale); 135 136 mShadowBitmap = getShadowBitmap(bounds.width(), bounds.height(), 137 (PROGRESS_GAP ) * scale); 138 mPathMeasure.setPath(mScaledTrackPath, true); 139 mTrackLength = mPathMeasure.getLength(); 140 141 setInternalProgress(mInternalStateProgress); 142 } 143 getShadowBitmap(int width, int height, float shadowRadius)144 private Bitmap getShadowBitmap(int width, int height, float shadowRadius) { 145 int key = (width << 16) | height; 146 WeakReference<Bitmap> shadowRef = sShadowCache.get(key); 147 Bitmap shadow = shadowRef != null ? shadowRef.get() : null; 148 if (shadow != null) { 149 return shadow; 150 } 151 shadow = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 152 Canvas c = new Canvas(shadow); 153 mProgressPaint.setShadowLayer(shadowRadius, 0, 0, COLOR_SHADOW); 154 mProgressPaint.setColor(COLOR_TRACK); 155 mProgressPaint.setAlpha(MAX_PAINT_ALPHA); 156 c.drawPath(mScaledTrackPath, mProgressPaint); 157 mProgressPaint.clearShadowLayer(); 158 c.setBitmap(null); 159 160 sShadowCache.put(key, new WeakReference<>(shadow)); 161 return shadow; 162 } 163 164 @Override drawInternal(Canvas canvas, Rect bounds)165 public void drawInternal(Canvas canvas, Rect bounds) { 166 if (mRanFinishAnimation) { 167 super.drawInternal(canvas, bounds); 168 return; 169 } 170 171 // Draw track. 172 mProgressPaint.setColor(mIndicatorColor); 173 mProgressPaint.setAlpha(mTrackAlpha); 174 if (mShadowBitmap != null) { 175 canvas.drawBitmap(mShadowBitmap, bounds.left, bounds.top, mProgressPaint); 176 } 177 canvas.drawPath(mScaledProgressPath, mProgressPaint); 178 179 int saveCount = canvas.save(); 180 canvas.scale(mIconScale, mIconScale, bounds.exactCenterX(), bounds.exactCenterY()); 181 super.drawInternal(canvas, bounds); 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(Interpolators.LINEAR); 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 * Sets the internal progress and updates the UI accordingly 244 * for progress <= 0: 245 * - icon in the small scale and disabled state 246 * - progress track is visible 247 * - progress bar is not visible 248 * for 0 < progress < 1 249 * - icon in the small scale and disabled state 250 * - progress track is visible 251 * - progress bar is visible with dominant color. Progress bar is drawn as a fraction of 252 * {@link #mScaledTrackPath}. 253 * @see PathMeasure#getSegment(float, float, Path, boolean) 254 * for 1 <= progress < (1 + COMPLETE_ANIM_FRACTION) 255 * - we calculate fraction of progress in the above range 256 * - progress track is drawn with alpha based on fraction 257 * - progress bar is drawn at 100% with alpha based on fraction 258 * - icon is scaled up based on fraction and is drawn in enabled state 259 * for progress >= (1 + COMPLETE_ANIM_FRACTION) 260 * - only icon is drawn in normal state 261 */ setInternalProgress(float progress)262 private void setInternalProgress(float progress) { 263 mInternalStateProgress = progress; 264 if (progress <= 0) { 265 mIconScale = SMALL_SCALE; 266 mScaledTrackPath.reset(); 267 mTrackAlpha = MAX_PAINT_ALPHA; 268 setIsDisabled(true); 269 } 270 271 if (progress < 1 && progress > 0) { 272 mPathMeasure.getSegment(0, progress * mTrackLength, mScaledProgressPath, true); 273 mIconScale = SMALL_SCALE; 274 mTrackAlpha = MAX_PAINT_ALPHA; 275 setIsDisabled(true); 276 } else if (progress >= 1) { 277 setIsDisabled(mItem.isDisabled()); 278 mScaledTrackPath.set(mScaledProgressPath); 279 float fraction = (progress - 1) / COMPLETE_ANIM_FRACTION; 280 281 if (fraction >= 1) { 282 // Animation has completed 283 mIconScale = 1; 284 mTrackAlpha = 0; 285 } else { 286 mTrackAlpha = Math.round((1 - fraction) * MAX_PAINT_ALPHA); 287 mIconScale = SMALL_SCALE + (1 - SMALL_SCALE) * fraction; 288 } 289 } 290 invalidateSelf(); 291 } 292 } 293