diff options
Diffstat (limited to 'src/com/android/launcher3/graphics/PreloadIconDrawable.java')
-rw-r--r-- | src/com/android/launcher3/graphics/PreloadIconDrawable.java | 289 |
1 files changed, 289 insertions, 0 deletions
diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java new file mode 100644 index 000000000..bc07ce1a5 --- /dev/null +++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.android.launcher3.graphics; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PathMeasure; +import android.graphics.Rect; +import android.util.Property; +import android.util.SparseArray; +import android.view.animation.LinearInterpolator; + +import com.android.launcher3.FastBitmapDrawable; + +import java.lang.ref.WeakReference; + +/** + * Extension of {@link FastBitmapDrawable} which shows a progress bar around the icon. + */ +public class PreloadIconDrawable extends FastBitmapDrawable { + + private static final Property<PreloadIconDrawable, Float> INTERNAL_STATE = + new Property<PreloadIconDrawable, Float>(Float.TYPE, "internalStateProgress") { + @Override + public Float get(PreloadIconDrawable object) { + return object.mInternalStateProgress; + } + + @Override + public void set(PreloadIconDrawable object, Float value) { + object.setInternalProgress(value); + } + }; + + public static final int PATH_SIZE = 100; + + private static final float PROGRESS_WIDTH = 7; + private static final float PROGRESS_GAP = 2; + private static final int MAX_PAINT_ALPHA = 255; + + private static final long DURATION_SCALE = 500; + + // The smaller the number, the faster the animation would be. + // Duration = COMPLETE_ANIM_FRACTION * DURATION_SCALE + private static final float COMPLETE_ANIM_FRACTION = 0.3f; + + private static final int COLOR_TRACK = 0x77EEEEEE; + private static final int COLOR_SHADOW = 0x55000000; + + private static final float SMALL_SCALE = 0.75f; + + private static final SparseArray<WeakReference<Bitmap>> sShadowCache = new SparseArray<>(); + + private final Matrix mTmpMatrix = new Matrix(); + private final PathMeasure mPathMeasure = new PathMeasure(); + + // Path in [0, 100] bounds. + private final Path mProgressPath; + + private final Path mScaledTrackPath; + private final Path mScaledProgressPath; + private final Paint mProgressPaint; + + private Bitmap mShadowBitmap; + private int mIndicatorColor = 0; + + private int mTrackAlpha; + private float mTrackLength; + private float mIconScale; + + private boolean mRanFinishAnimation; + + // Progress of the internal state. [0, 1] indicates the fraction of completed progress, + // [1, (1 + COMPLETE_ANIM_FRACTION)] indicates the progress of zoom animation. + private float mInternalStateProgress; + + private ObjectAnimator mCurrentAnim; + + /** + * @param progressPath fixed path in the bounds [0, 0, 100, 100] representing a progress bar. + */ + public PreloadIconDrawable(Bitmap b, Path progressPath) { + super(b); + mProgressPath = progressPath; + mScaledTrackPath = new Path(); + mScaledProgressPath = new Path(); + + mProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); + mProgressPaint.setStyle(Paint.Style.STROKE); + mProgressPaint.setStrokeCap(Paint.Cap.ROUND); + + setInternalProgress(0); + } + + @Override + protected void onBoundsChange(Rect bounds) { + super.onBoundsChange(bounds); + mTmpMatrix.setScale( + (bounds.width() - PROGRESS_WIDTH - 2 * PROGRESS_GAP) / PATH_SIZE, + (bounds.height() - PROGRESS_WIDTH - 2 * PROGRESS_GAP) / PATH_SIZE); + mTmpMatrix.postTranslate( + bounds.left + PROGRESS_WIDTH / 2 + PROGRESS_GAP, + bounds.top + PROGRESS_WIDTH / 2 + PROGRESS_GAP); + + mProgressPath.transform(mTmpMatrix, mScaledTrackPath); + float scale = bounds.width() / PATH_SIZE; + mProgressPaint.setStrokeWidth(PROGRESS_WIDTH * scale); + + mShadowBitmap = getShadowBitmap(bounds.width(), bounds.height(), + (PROGRESS_GAP ) * scale); + mPathMeasure.setPath(mScaledTrackPath, true); + mTrackLength = mPathMeasure.getLength(); + + setInternalProgress(mInternalStateProgress); + } + + private Bitmap getShadowBitmap(int width, int height, float shadowRadius) { + int key = (width << 16) | height; + WeakReference<Bitmap> shadowRef = sShadowCache.get(key); + Bitmap shadow = shadowRef != null ? shadowRef.get() : null; + if (shadow != null) { + return shadow; + } + shadow = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(shadow); + mProgressPaint.setShadowLayer(shadowRadius, 0, 0, COLOR_SHADOW); + mProgressPaint.setColor(COLOR_TRACK); + mProgressPaint.setAlpha(MAX_PAINT_ALPHA); + c.drawPath(mScaledTrackPath, mProgressPaint); + mProgressPaint.clearShadowLayer(); + c.setBitmap(null); + + sShadowCache.put(key, new WeakReference<>(shadow)); + return shadow; + } + + @Override + public void draw(Canvas canvas) { + if (mRanFinishAnimation) { + super.draw(canvas); + return; + } + + // Draw track. + mProgressPaint.setColor(mIndicatorColor); + mProgressPaint.setAlpha(mTrackAlpha); + if (mShadowBitmap != null) { + canvas.drawBitmap(mShadowBitmap, getBounds().left, getBounds().top, mProgressPaint); + } + canvas.drawPath(mScaledProgressPath, mProgressPaint); + + int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG); + Rect bounds = getBounds(); + + canvas.scale(mIconScale, mIconScale, bounds.exactCenterX(), bounds.exactCenterY()); + drawInternal(canvas); + canvas.restoreToCount(saveCount); + } + + /** + * Updates the install progress based on the level + */ + @Override + protected boolean onLevelChange(int level) { + // Run the animation if we have already been bound. + updateInternalState(level * 0.01f, getBounds().width() > 0, false); + return true; + } + + /** + * Runs the finish animation if it is has not been run after last call to + * {@link #onLevelChange} + */ + public void maybePerformFinishedAnimation() { + // If the drawable was recently initialized, skip the progress animation. + if (mInternalStateProgress == 0) { + mInternalStateProgress = 1; + } + updateInternalState(1 + COMPLETE_ANIM_FRACTION, true, true); + } + + public boolean hasNotCompleted() { + return !mRanFinishAnimation; + } + + private void updateInternalState(float finalProgress, boolean shouldAnimate, boolean isFinish) { + if (mCurrentAnim != null) { + mCurrentAnim.cancel(); + mCurrentAnim = null; + } + + if (Float.compare(finalProgress, mInternalStateProgress) == 0) { + return; + } + if (!shouldAnimate || mRanFinishAnimation) { + setInternalProgress(finalProgress); + } else { + mCurrentAnim = ObjectAnimator.ofFloat(this, INTERNAL_STATE, finalProgress); + mCurrentAnim.setDuration( + (long) ((finalProgress - mInternalStateProgress) * DURATION_SCALE)); + mCurrentAnim.setInterpolator(new LinearInterpolator()); + if (isFinish) { + mCurrentAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mRanFinishAnimation = true; + } + }); + } + mCurrentAnim.start(); + } + + } + + /** + * Sets the internal progress and updates the UI accordingly + * for progress <= 0: + * - icon in the small scale and disabled state + * - progress track is visible + * - progress bar is not visible + * for 0 < progress < 1 + * - icon in the small scale and disabled state + * - progress track is visible + * - progress bar is visible with dominant color. Progress bar is drawn as a fraction of + * {@link #mScaledTrackPath}. + * @see PathMeasure#getSegment(float, float, Path, boolean) + * for 1 <= progress < (1 + COMPLETE_ANIM_FRACTION) + * - we calculate fraction of progress in the above range + * - progress track is drawn with alpha based on fraction + * - progress bar is drawn at 100% with alpha based on fraction + * - icon is scaled up based on fraction and is drawn in enabled state + * for progress >= (1 + COMPLETE_ANIM_FRACTION) + * - only icon is drawn in normal state + */ + private void setInternalProgress(float progress) { + mInternalStateProgress = progress; + if (progress <= 0) { + mIconScale = SMALL_SCALE; + mScaledTrackPath.reset(); + mTrackAlpha = MAX_PAINT_ALPHA; + setIsDisabled(true); + } else if (mIndicatorColor == 0) { + // Update the indicator color + mIndicatorColor = getIconPalette().getPreloadProgressColor(); + } + + if (progress < 1 && progress > 0) { + mPathMeasure.getSegment(0, progress * mTrackLength, mScaledProgressPath, true); + mIconScale = SMALL_SCALE; + mTrackAlpha = MAX_PAINT_ALPHA; + setIsDisabled(true); + } else if (progress >= 1) { + setIsDisabled(false); + mScaledTrackPath.set(mScaledProgressPath); + float fraction = (progress - 1) / COMPLETE_ANIM_FRACTION; + + if (fraction >= 1) { + // Animation has completed + mIconScale = 1; + mTrackAlpha = 0; + } else { + mTrackAlpha = Math.round((1 - fraction) * MAX_PAINT_ALPHA); + mIconScale = SMALL_SCALE + (1 - SMALL_SCALE) * fraction; + } + } + invalidateSelf(); + } +} |