From 95abbb330ce9bbaf23594245f0f8a795c8118038 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Mon, 4 Aug 2014 10:53:22 -0700 Subject: Updating the virtual preloader UX. > No click feedback when in preloader mode > No preloader UI when drawn in drag layer > The preloader consists of a background 9 patch image and a circular progress is drawn in the content region of the background. > The preloader is drawn in a slightly larget area than the actual bounds to make the circular progress more prominent compared to the icon. issue: 15835307 Change-Id: Ifec3d93ecf1fac994d1128b517da3797247e7ed6 --- src/com/android/launcher3/PreloadIconDrawable.java | 173 +++++++++++++++------ 1 file changed, 127 insertions(+), 46 deletions(-) (limited to 'src/com/android/launcher3/PreloadIconDrawable.java') diff --git a/src/com/android/launcher3/PreloadIconDrawable.java b/src/com/android/launcher3/PreloadIconDrawable.java index d9365cc1f..2972c4f9b 100644 --- a/src/com/android/launcher3/PreloadIconDrawable.java +++ b/src/com/android/launcher3/PreloadIconDrawable.java @@ -1,96 +1,143 @@ package com.android.launcher3; import android.animation.ObjectAnimator; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; +import android.content.res.Resources.Theme; +import android.content.res.TypedArray; import android.graphics.Canvas; +import android.graphics.Color; 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 final float MIN_SATUNATION = 0.2f; + private static final float MIN_LIGHTNESS = 0.6f; + + private static final float ICON_SCALE_FACTOR = 0.5f; + private static final int DEFAULT_COLOR = 0xFF009688; - private static Bitmap sProgressBg, sProgressFill; + private static final Rect sTempRect = new Rect(); - 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); + private final RectF mIndicatorRect = new RectF(); + private boolean mIndicatorRectDirty; + private final Paint mPaint; final Drawable mIcon; + private Drawable mBgDrawable; + private int mRingOutset; + + private int mIndicatorColor = 0; + /** * 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) { + public PreloadIconDrawable(Drawable icon, Theme theme) { mIcon = icon; + mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mPaint.setStyle(Paint.Style.STROKE); + mPaint.setStrokeCap(Paint.Cap.ROUND); + setBounds(icon.getBounds()); - mPathChanged = false; + applyTheme(theme); + onLevelChange(0); + } - 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 applyTheme(Theme t) { + TypedArray ta = t.obtainStyledAttributes(R.styleable.PreloadIconDrawable); + mBgDrawable = ta.getDrawable(R.styleable.PreloadIconDrawable_background); + mBgDrawable.setFilterBitmap(true); + mPaint.setStrokeWidth(ta.getDimension(R.styleable.PreloadIconDrawable_indicatorSize, 0)); + mRingOutset = ta.getDimensionPixelSize(R.styleable.PreloadIconDrawable_ringOutset, 0); + ta.recycle(); + onBoundsChange(getBounds()); + invalidateSelf(); + } + + @Override + protected void onBoundsChange(Rect bounds) { + mIcon.setBounds(bounds); + if (mBgDrawable != null) { + sTempRect.set(bounds); + sTempRect.inset(-mRingOutset, -mRingOutset); + mBgDrawable.setBounds(sTempRect); } + mIndicatorRectDirty = true; + } + + public int getOutset() { + return mRingOutset; + } + + /** + * The size of the indicator is same as the content region of the {@link #mBgDrawable} minus + * half the stroke size to accommodate the indicator. + */ + private void initIndicatorRect() { + Drawable d = mBgDrawable; + Rect bounds = d.getBounds(); + + d.getPadding(sTempRect); + // Amount by which padding has to be scaled + float paddingScaleX = ((float) bounds.width()) / d.getIntrinsicWidth(); + float paddingScaleY = ((float) bounds.height()) / d.getIntrinsicHeight(); + mIndicatorRect.set( + bounds.left + sTempRect.left * paddingScaleX, + bounds.top + sTempRect.top * paddingScaleY, + bounds.right - sTempRect.right * paddingScaleX, + bounds.bottom - sTempRect.bottom * paddingScaleY); + + float inset = mPaint.getStrokeWidth() / 2; + mIndicatorRect.inset(inset, inset); + mIndicatorRectDirty = false; } @Override public void draw(Canvas canvas) { - final Rect r = getBounds(); - if (canvas.getClipBounds(mCanvasClipRect) && !Rect.intersects(mCanvasClipRect, r)) { + final Rect r = new Rect(getBounds()); + if (canvas.getClipBounds(sTempRect) && !Rect.intersects(sTempRect, r)) { // The draw region has been clipped. return; } + if (mIndicatorRectDirty) { + initIndicatorRect(); + } 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; + mBgDrawable.setAlpha(mPaint.getAlpha()); + mBgDrawable.draw(canvas); + canvas.drawOval(mIndicatorRect, 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); + mBgDrawable.setAlpha(255); + mBgDrawable.draw(canvas); if (mProgress >= 100) { - canvas.drawBitmap(sProgressFill, null, r, mPaint); + canvas.drawOval(mIndicatorRect, 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(); + canvas.drawArc(mIndicatorRect, -90, mProgress * 3.6f, false, mPaint); } } else { iconScale = 1; @@ -102,12 +149,6 @@ class PreloadIconDrawable extends Drawable { canvas.restore(); } - @Override - protected void onBoundsChange(Rect bounds) { - mIcon.setBounds(bounds); - mPathChanged = true; - } - @Override public int getOpacity() { return PixelFormat.TRANSLUCENT; @@ -126,7 +167,6 @@ class PreloadIconDrawable extends Drawable { @Override protected boolean onLevelChange(int level) { mProgress = level; - mPathChanged = true; // Stop Animation if (mAnimator != null) { @@ -134,6 +174,14 @@ class PreloadIconDrawable extends Drawable { mAnimator = null; } mAnimationProgress = ANIMATION_PROGRESS_STOPPED; + if (level > 0) { + // Set the paint color only when the level changes, so that the dominant color + // is only calculated when needed. + mPaint.setColor(getIndicatorColor()); + } + if (mIcon instanceof FastBitmapDrawable) { + ((FastBitmapDrawable) mIcon).setGhostModeEnabled(level <= 0); + } invalidateSelf(); return true; @@ -165,4 +213,37 @@ class PreloadIconDrawable extends Drawable { public float getAnimationProgress() { return mAnimationProgress; } + + @Override + public int getIntrinsicHeight() { + return mIcon.getIntrinsicHeight(); + } + + @Override + public int getIntrinsicWidth() { + return mIcon.getIntrinsicWidth(); + } + + private int getIndicatorColor() { + if (mIndicatorColor != 0) { + return mIndicatorColor; + } + if (!(mIcon instanceof FastBitmapDrawable)) { + mIndicatorColor = DEFAULT_COLOR; + return mIndicatorColor; + } + mIndicatorColor = Utilities.findDominantColorByHue( + ((FastBitmapDrawable) mIcon).getBitmap(), 20); + + // Make sure that the dominant color has enough saturation to be visible properly. + float[] hsv = new float[3]; + Color.colorToHSV(mIndicatorColor, hsv); + if (hsv[1] < MIN_SATUNATION) { + mIndicatorColor = DEFAULT_COLOR; + return mIndicatorColor; + } + hsv[2] = Math.max(MIN_LIGHTNESS, hsv[2]); + mIndicatorColor = Color.HSVToColor(hsv); + return mIndicatorColor; + } } -- cgit v1.2.3