From 3484638cad97e255a412b0489a63873fb3ca4218 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Wed, 9 Jul 2014 00:09:28 -0700 Subject: Adding a circular progress bar for preloader icons Change-Id: I1b5ba61c01a16a8cb5d3f9e31f827f8c99a1ffc9 --- proguard.flags | 5 + res/drawable-xxhdpi/bg_preloader.png | Bin 0 -> 3785 bytes res/drawable-xxhdpi/bg_preloader_progress.png | Bin 0 -> 1131 bytes res/values/integers.xml | 22 --- src/com/android/launcher3/BubbleTextView.java | 28 +++- src/com/android/launcher3/FolderIcon.java | 9 +- src/com/android/launcher3/LauncherModel.java | 1 + src/com/android/launcher3/PreloadIconDrawable.java | 168 +++++++++++++++++++++ src/com/android/launcher3/ShortcutInfo.java | 11 +- 9 files changed, 208 insertions(+), 36 deletions(-) create mode 100644 res/drawable-xxhdpi/bg_preloader.png create mode 100644 res/drawable-xxhdpi/bg_preloader_progress.png delete mode 100644 res/values/integers.xml create mode 100644 src/com/android/launcher3/PreloadIconDrawable.java diff --git a/proguard.flags b/proguard.flags index a922e919d..0b28c0ef4 100644 --- a/proguard.flags +++ b/proguard.flags @@ -52,3 +52,8 @@ -keep class com.android.launcher3.MemoryDumpActivity { *; } + +-keep class com.android.launcher3.PreloadIconDrawable { + public float getAnimationProgress(); + public void setAnimationProgress(float); +} diff --git a/res/drawable-xxhdpi/bg_preloader.png b/res/drawable-xxhdpi/bg_preloader.png new file mode 100644 index 000000000..56b80607d Binary files /dev/null and b/res/drawable-xxhdpi/bg_preloader.png differ diff --git a/res/drawable-xxhdpi/bg_preloader_progress.png b/res/drawable-xxhdpi/bg_preloader_progress.png new file mode 100644 index 000000000..443afe936 Binary files /dev/null and b/res/drawable-xxhdpi/bg_preloader_progress.png differ diff --git a/res/values/integers.xml b/res/values/integers.xml deleted file mode 100644 index 7d26d8595..000000000 --- a/res/values/integers.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - 127 - \ No newline at end of file diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 57dcea044..3f619a812 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -136,7 +136,8 @@ public class BubbleTextView extends TextView { setContentDescription(info.contentDescription); } setTag(info); - if (info.isPromise()) { + + if (info.wasPromise) { applyState(); } } @@ -431,42 +432,55 @@ public class BubbleTextView extends TextView { } public void applyState() { - int alpha = getResources().getInteger(R.integer.promise_icon_alpha); + final int progressLevel; final int state = getState(); if (DEBUG) Log.d(TAG, "applying icon state: " + state); switch(state) { case ShortcutInfo.PACKAGE_STATE_DEFAULT: super.setText(mDefaultText); - alpha = 255; + progressLevel = 100; break; case ShortcutInfo.PACKAGE_STATE_ENQUEUED: setText(R.string.package_state_enqueued); + progressLevel = 0; break; case ShortcutInfo.PACKAGE_STATE_DOWNLOADING: setText(R.string.package_state_downloading); + // TODO(sunnygoyal): fix progress + progressLevel = 30; break; case ShortcutInfo.PACKAGE_STATE_INSTALLING: setText(R.string.package_state_installing); + progressLevel = 100; break; case ShortcutInfo.PACKAGE_STATE_ERROR: setText(R.string.package_state_error); + progressLevel = 0; break; case ShortcutInfo.PACKAGE_STATE_UNKNOWN: default: + progressLevel = 0; setText(R.string.package_state_unknown); break; } - if (DEBUG) Log.d(TAG, "setting icon alpha to: " + alpha); + Drawable[] drawables = getCompoundDrawables(); - for (int i = 0; i < drawables.length; i++) { - if (drawables[i] != null) { - drawables[i].setAlpha(alpha); + Drawable top = drawables[1]; + if ((top != null) && !(top instanceof PreloadIconDrawable)) { + top = new PreloadIconDrawable(top, getResources()); + setCompoundDrawables(drawables[0], top, drawables[2], drawables[3]); + } + if (top != null) { + top.setLevel(progressLevel); + if ((top instanceof PreloadIconDrawable) + && (state == ShortcutInfo.PACKAGE_STATE_DEFAULT)) { + ((PreloadIconDrawable) top).maybePerformFinishedAnimation(); } } } diff --git a/src/com/android/launcher3/FolderIcon.java b/src/com/android/launcher3/FolderIcon.java index ab8976a59..4f674f55a 100644 --- a/src/com/android/launcher3/FolderIcon.java +++ b/src/com/android/launcher3/FolderIcon.java @@ -605,7 +605,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { computePreviewDrawingParams(mAnimParams.drawable); } else { v = (TextView) items.get(0); - d = v.getCompoundDrawables()[1]; + d = getTopDrawable(v); computePreviewDrawingParams(d); } @@ -614,7 +614,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { for (int i = nItemsInPreview - 1; i >= 0; i--) { v = (TextView) items.get(i); if (!mHiddenItems.contains(v.getTag())) { - d = v.getCompoundDrawables()[1]; + d = getTopDrawable(v); mParams = computePreviewItemDrawingParams(i, mParams); mParams.drawable = d; drawPreviewItem(canvas, mParams); @@ -625,6 +625,11 @@ public class FolderIcon extends FrameLayout implements FolderListener { } } + private Drawable getTopDrawable(TextView v) { + Drawable d = v.getCompoundDrawables()[1]; + return (d instanceof PreloadIconDrawable) ? ((PreloadIconDrawable) d).mIcon : d; + } + private void animateFirstItem(final Drawable d, int duration, final boolean reverse, final Runnable onCompleteRunnable) { final PreviewItemDrawingParams finalParams = computePreviewItemDrawingParams(0, null); diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index f1e73eb1d..b01db7194 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -3047,6 +3047,7 @@ public class LauncherModel extends BroadcastReceiver info.setIcon(mIconCache.getIcon(intent, info.title.toString(), info.user)); info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; info.restoredIntent = intent; + info.wasPromise = true; info.setState(ShortcutInfo.PACKAGE_STATE_UNKNOWN); return info; } diff --git a/src/com/android/launcher3/PreloadIconDrawable.java b/src/com/android/launcher3/PreloadIconDrawable.java new file mode 100644 index 000000000..d9365cc1f --- /dev/null +++ b/src/com/android/launcher3/PreloadIconDrawable.java @@ -0,0 +1,168 @@ +package com.android.launcher3; + +import android.animation.ObjectAnimator; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; + +class PreloadIconDrawable extends Drawable { + private static final float ANIMATION_PROGRESS_STOPPED = -1.0f; + private static final float ANIMATION_PROGRESS_STARTED = 0f; + private static final float ANIMATION_PROGRESS_COMPLETED = 1.0f; + + private static final float ICON_SCALE_FACTOR = 0.6f; + + private static Bitmap sProgressBg, sProgressFill; + + private final Rect mCanvasClipRect = new Rect(); + private final RectF mRect = new RectF(); + private final Path mProgressPath = new Path(); + private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG); + + final Drawable mIcon; + + /** + * Indicates the progress of the preloader [0-100]. If it goes above 100, only the icon + * is shown with no progress bar. + */ + private int mProgress = 0; + private boolean mPathChanged; + + private float mAnimationProgress = ANIMATION_PROGRESS_STOPPED; + private ObjectAnimator mAnimator; + + public PreloadIconDrawable(Drawable icon, Resources res) { + mIcon = icon; + + setBounds(icon.getBounds()); + mPathChanged = false; + + if (sProgressBg == null) { + sProgressBg = BitmapFactory.decodeResource(res, R.drawable.bg_preloader); + } + if (sProgressFill == null) { + sProgressFill = BitmapFactory.decodeResource(res, R.drawable.bg_preloader_progress); + } + } + + @Override + public void draw(Canvas canvas) { + final Rect r = getBounds(); + if (canvas.getClipBounds(mCanvasClipRect) && !Rect.intersects(mCanvasClipRect, r)) { + // The draw region has been clipped. + return; + } + final float iconScale; + + if ((mAnimationProgress >= ANIMATION_PROGRESS_STARTED) + && (mAnimationProgress < ANIMATION_PROGRESS_COMPLETED)) { + mPaint.setAlpha((int) ((1 - mAnimationProgress) * 255)); + canvas.drawBitmap(sProgressBg, null, r, mPaint); + canvas.drawBitmap(sProgressFill, null, r, mPaint); + iconScale = ICON_SCALE_FACTOR + (1 - ICON_SCALE_FACTOR) * mAnimationProgress; + + } else if (mAnimationProgress == ANIMATION_PROGRESS_STOPPED) { + mPaint.setAlpha(255); + iconScale = ICON_SCALE_FACTOR; + canvas.drawBitmap(sProgressBg, null, r, mPaint); + + if (mProgress >= 100) { + canvas.drawBitmap(sProgressFill, null, r, mPaint); + } else if (mProgress > 0) { + if (mPathChanged) { + mProgressPath.reset(); + mProgressPath.moveTo(r.exactCenterX(), r.centerY()); + + mRect.set(r); + mProgressPath.arcTo(mRect, -90, mProgress * 3.6f); + mProgressPath.close(); + mPathChanged = false; + } + + canvas.save(); + canvas.clipPath(mProgressPath); + canvas.drawBitmap(sProgressFill, null, r, mPaint); + canvas.restore(); + } + } else { + iconScale = 1; + } + + canvas.save(); + canvas.scale(iconScale, iconScale, r.exactCenterX(), r.exactCenterY()); + mIcon.draw(canvas); + canvas.restore(); + } + + @Override + protected void onBoundsChange(Rect bounds) { + mIcon.setBounds(bounds); + mPathChanged = true; + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + + @Override + public void setAlpha(int alpha) { + mIcon.setAlpha(alpha); + } + + @Override + public void setColorFilter(ColorFilter cf) { + mIcon.setColorFilter(cf); + } + + @Override + protected boolean onLevelChange(int level) { + mProgress = level; + mPathChanged = true; + + // Stop Animation + if (mAnimator != null) { + mAnimator.cancel(); + mAnimator = null; + } + mAnimationProgress = ANIMATION_PROGRESS_STOPPED; + + invalidateSelf(); + return true; + } + + /** + * Runs the finish animation if it is has not been run after last level change. + */ + public void maybePerformFinishedAnimation() { + if (mAnimationProgress > ANIMATION_PROGRESS_STOPPED) { + return; + } + if (mAnimator != null) { + mAnimator.cancel(); + } + setAnimationProgress(ANIMATION_PROGRESS_STARTED); + mAnimator = ObjectAnimator.ofFloat(this, "animationProgress", + ANIMATION_PROGRESS_STARTED, ANIMATION_PROGRESS_COMPLETED); + mAnimator.start(); + } + + public void setAnimationProgress(float progress) { + if (progress != mAnimationProgress) { + mAnimationProgress = progress; + invalidateSelf(); + } + } + + public float getAnimationProgress() { + return mAnimationProgress; + } +} diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java index d2573a4a1..7e1f0d649 100644 --- a/src/com/android/launcher3/ShortcutInfo.java +++ b/src/com/android/launcher3/ShortcutInfo.java @@ -16,13 +16,9 @@ package com.android.launcher3; -import android.content.ComponentName; import android.content.ContentValues; import android.content.Context; import android.content.Intent; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; import android.graphics.Bitmap; import android.util.Log; @@ -96,6 +92,11 @@ public class ShortcutInfo extends ItemInfo { */ Intent restoredIntent; + /** + * This is set once to indicate that it was a promise info at some point of its life. + */ + boolean wasPromise = false; + ShortcutInfo() { itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT; } @@ -119,7 +120,7 @@ public class ShortcutInfo extends ItemInfo { } } - ShortcutInfo(Intent intent, CharSequence title, String contentDescrition, + ShortcutInfo(Intent intent, CharSequence title, String contentDescription, Bitmap icon, UserHandleCompat user) { this(); this.intent = intent; -- cgit v1.2.3